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


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

ちょうど[こういう記事](https://qiita.com/akiray03/items/3607c60ec8b221b3c2ba)を見つけたのでrubyの勉強にもなるし一石二鳥とういうことでcrystalのsocketライブラリでwebサーバーを書いていきます

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

```rb: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リクエストに応答するようにしてみます

```rb: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との違いを見て取ることができますね

```rb: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
```

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

```rb: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は存在していないようですね](https://tmtms.hatenablog.com/entry/2015/12/22/crystal)
spawnを使うといい感じにできるようなのでやってみましょう

```rb: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的な書きやすさと静的型付けの安全性を兼ね備えた上に速いのは強いなって思った
  - 個人的には好きなので流行ってほしい