概要
ずいぶん前に某所にて静的型付けな言語の良さについて洗脳教えを受けたはいいものの結局静的型付けやってないなと思っていたら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的な書きやすさと静的型付けの安全性を兼ね備えた上に速いのは強いなって思った
- 個人的には好きなので流行ってほしい