人権真骨頂

とくがたかいことでゆうめい

Crystalで実装したHTTPサーバーをHerokuにデプロイする

Crystal書いたこと無いけど,やってみる.試行錯誤のログ.ちなみに,Rubyは書けない.

ここをfmfmと言いながら読んでみたくらい.

ja.crystal-lang.org

インストール

macOSだとHomebrewで入る.

$ brew update
$ brew install crystal-lang

プロジェクト作成

$ crystal init app crystal-sample-http-server
      create  crystal-sample-http-server/.gitignore
      create  crystal-sample-http-server/LICENSE
      create  crystal-sample-http-server/README.md
      create  crystal-sample-http-server/.travis.yml
      create  crystal-sample-http-server/shard.yml
      create  crystal-sample-http-server/src/crystal-sample-http-server.cr
      create  crystal-sample-http-server/src/crystal-sample-http-server/version.cr
      create  crystal-sample-http-server/spec/spec_helper.cr
      create  crystal-sample-http-server/spec/crystal-sample-http-server_spec.cr
Initialized empty Git repository in /Users/upamune/src/github.com/upamune/crystal-sample-http-server/.git/

これでプロジェクト作成できる.今っぽい.

.travis.yml まで生成されるのか.

ディレクトリ構成がGoなのは気にしない.

HTTPサーバー実装

/ に来たら, 200 OKHello, Crystal というレスポンスを返すHTTPサーバーを標準ライブラリで実装してみる.

標準ライブラリのドキュメントはここを見れば良さそう.

README - github.com/crystal-lang/crystal

ドキュメントのバージョンと利用しているCrystalのバージョンがあっているか注意.

$ crystal verion
Crystal 0.19.2 (2016-09-16)

HTTP - github.com/crystal-lang/crystal

おそらくココらへんに何か書いてそう.

HTTP::Server - github.com/crystal-lang/crystal

これか.

require "http/server"

...

  server = HTTP::Server.new("0.0.0.0", port, [
    HTTP::ErrorHandler.new,
    HTTP::LogHandler.new,
  ]) do |context|
    context.response.content_type = "text/plain"
    context.response.status_code = 200
    context.response.print "Hello world!"
  end

puts "Listening on http://0.0.0.0:8080"
server.listen

参考にして書いてみた.ログを出すようにミドルウェアを追加しておいた.

HTTP::Server::Response - github.com/crystal-lang/crystal

ステータスコードはこれでいじれる. HTTP::Server::Response#status_code - github.com/crystal-lang/crystal

デフォルトが 200 なので,今回はいじらなくて良いけど,明示的に書いておく.

これで必要な処理は書いた気がするので,走らせてみる.

$ crystal build src/crystal-sample-http-server.cr
$ ./crystal-sample-http-server
Listening on http://0.0.0.0:8080

$ http localhost:8080/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 15
Content-Type: text/plain
Hello, Crystal.

おお.動いた. build サブコマンドは o オプションで吐き出すバイナリの名前を変更できる.あと, 本番時は --release を付けてビルドするらしい.

大体できたけど, / だけ200 を返して,他は404 返すようにしたい.ルーティングどうやんだろ.

request からパスが取れるっぽいのでそれを使ってマッチさせる.

HTTP::Request#path - github.com/crystal-lang/crystal

  server = HTTP::Server.new("0.0.0.0", port, [
    HTTP::ErrorHandler.new,
    HTTP::LogHandler.new,
  ]) do |context|
    case context.request.path
    when "/"
      context.response.content_type = "text/plain"
      context.response.status_code = 200
      context.response.print "Hello, Crystal."
    else
      context.response.status_code = 404
    end
  end

一応これでクソ雑ルーティングっぽいものはできた.

$ http localhost:8080/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 15
Content-Type: text/plain

Hello, Crystal.
$ http localhost:8080/hoge
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 0

よしよし.

後でググったら,Sinatraっぽくやってるのもあった.

Basic Routing in Crystal HTTP Server | Motion Express | Ruby, Rails, Crystal & developers' techniques

ハッシュにパスとそれに対応するProcを登録していく感じ.

一応動くようにはなったので,Herokuにデプロイしていく.

Herokuにデプロイするための準備

公式のビルドパックはないので,これを利用する.

crystal-lang/heroku-buildpack-crystal: Heroku buildpack for Crystal

最新のバージョンのCrystalを利用して動くらしいが, .crystal-version がプロジェクトのルートにあると,そこに記述されているバージョンを利用するらしい.

準備としてはもう一つあって, このビルドパックが,アプリケーションを走らせるときに --port でポート番号を指定してきているので,このオプションに対応する必要があるので,少し追加する.

OptionParser - github.com/crystal-lang/crystal

ここを読んでみる.

require "option_parser"

...

  port = 8080

  OptionParser.parse! do |parser|
      parser.on("-p PORT", "--port=PORT", "Specifies the port") { |port_str| 
        port = port_str.to_i if port_str.to_i? 
      }
  end

こんな感じで,一応ポート番号をオプションで指定できるようになったのでこれで良さそう.

準備ができたので,デプロイしてみる.

Herokuにデプロイ

$ heroku create crystal-sample-http-server --buildpack https://github.com/crystal-lang/heroku-buildpack-crystal.git
$ git push heroku master
$ heroku open

オ,ちゃんと表示されてる.

$ http https://crystal-sample-http-server.herokuapp.com/
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 15
Content-Type: text/plain
Date: Thu, 22 Sep 2016 15:12:05 GMT
Server: Cowboy
Via: 1.1 vegur

Hello, Crystal.

$ http https://crystal-sample-http-server.herokuapp.com/hoge
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 0
Date: Thu, 22 Sep 2016 15:12:08 GMT
Server: Cowboy
Via: 1.1 vegur

良い.

まぁ普通はWAFを利用するんだけど,有名どころだと,このへんなのかな?

github.com

更新もされている.Sinatraとの比較が載っているけど,まぁ速い.

サイトもちゃんとある.

kemalcr.com

これも触って,試してみたい.

ちなみに今回のやつは👇に置いてある.

github.com