LoginSignup
6
1

More than 5 years have passed since last update.

crystal試すのにsocketでwebサーバーを書いてみた

Last updated at Posted at 2018-09-07

概要

ずいぶん前に某所にて静的型付けな言語の良さについて洗脳教えを受けたはいいものの結局静的型付けやってないなと思っていたらcrystalなる言語を知ったの触れてみることにしました

ちょうどこういう記事を見つけたのでrubyの勉強にもなるし一石二鳥とういうことでcrystalのsocketライブラリでwebサーバーを書いていきます

TCPサーバー

まず元の記事と同じようにTCPサーバから書いていきます

crystal
require "socket"

server = TCPServer.new 2000
counter : Int32 = 0
loop do
  client = server.accept
  p client
  client.puts "Hello #{Time.now} counter:#{counter}"
  client.close
  counter += 1
end

びっくりするほどruby
せいぜいカウンターに型をつけてみた程度
実行してみると問題なく動きます

$ nc 127.0.0.1 2000
Hello 2018-09-02 23:08:57 +09:00 counter:0
$ nc 127.0.0.1 2000
Hello 2018-09-02 23:08:58 +09:00 counter:1
$ nc 127.0.0.1 2000
Hello 2018-09-02 23:08:59 +09:00 counter:2

次にTCPリクエストに応答するようにしてみます

crystal
require "socket"

server = TCPServer.new 2000
counter : Int32 = 0
loop do
  client = server.accept
  p client
  while buffer = client.gets
    puts buffer
  end
  client.puts "Hello #{Time.now} counter:#{counter}"
  client.close
  counter += 1
end

やはり結果も同じ
rubyとほぼ同じコードで動きました

$ echo "新たな光に会いに行こう" | nc 127.0.0.1 2000
Hello 2018-09-02 23:21:24 +09:00 counter:0
$ crystal src/echo.cr
#<TCPSocket:fd 8>
新たな光に会いに行こう

HTTPサーバー

最低限のHTTPサーバー
とりあえずはrubyと同じように書けます
ただ空の配列の作り方の違いにrubyとの違いを見て取ることができますね

crystal
require "socket"

server = TCPServer.new(2000)
loop do
  socket = server.accept
  headers = Array(String).new
  while header = socket.gets
    break if header.chomp.empty?
    headers << header.chomp
  end
  p headers

  socket.puts "HTTP/1.0 200 OK"
  socket.puts "Content-Type: text/plain"
  socket.puts
  socket.puts "新たな光に会いに行こう"
  socket.close
end
$ crystal src/echo.cr
["GET / HTTP/1.1", "Host: 127.0.0.1:2000", "User-Agent: curl/7.54.0", "Accept: */*"]
$ curl -v http://127.0.0.1:2000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 2000 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:2000
> User-Agent: curl/7.54.0
> Accept: */*
> 
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: text/plain
< 
新たな光に会いに行こう
* Closing connection 0

ここまでは問題なく動きました
では並列化はどうでしょうか?
試してみましょう

crystal
require "socket"

server = TCPServer.new(2000)
loop do
  Thread.start(server.accept) do |socket|
    p [Thread.current]
    headers = Array(String).new
    while header = socket.gets
      break if header.chomp.empty?
      headers << header.chomp
    end
    p [Thread.current, headers]

    socket.puts "HTTP/1.0 200 OK"
    socket.puts "Content-Type: text/plain"
    socket.puts
    socket.puts "新たな光に会いに行こう"
    socket.close
  end
end
$ crystal src/echo.cr
Error in src/echo.cr:7: instantiating 'loop()'

  loop do
  ^~~~

in src/echo.cr:7: instantiating 'loop()'

  loop do
  ^~~~

in src/echo.cr:8: undefined method 'start' for Thread:Class

    Thread.start(server.accept) do |socket|
           ^~~~~

エラーになりました
どうやらcrystalではThread.startは存在していないようですね
spawnを使うといい感じにできるようなのでやってみましょう

crystal
require "socket"

server = TCPServer.new(2000)
loop do
  socket = server.accept
  spawn do
    headers = Array(String).new
    while header = socket.gets
      break if header.chomp.empty?
      headers << header.chomp
    end
    p headers

    socket.puts "HTTP/1.0 200 OK"
    socket.puts "Content-Type: text/plain"
    socket.puts
    socket.puts "新たな光に会いに行こう"
    socket.close
  end
end
$ nc 127.0.0.1 2000
$ curl -v http://127.0.0.1:2000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 2000 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:2000
> User-Agent: curl/7.54.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: text/plain
<
新たな光に会いに行こう
* Closing connection 0
$ crystal src/echo.cr
["GET / HTTP/1.1", "Host: 127.0.0.1:2000", "User-Agent: curl/7.54.0", "Accept: */*"]

うまく動作しましたね

まとめ

  • ほぼ自分でコード書かなかったのではと思うくらいに元の記事のrubyのままで動いてしまった
  • ただ一方でrubyとの違いというのもところどころ見受けられた
  • ruby的な書きやすさと静的型付けの安全性を兼ね備えた上に速いのは強いなって思った
  • 個人的には好きなので流行ってほしい
6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1