はじめに
Trusterdはこのアドベントカレンダーの主催者の@matsumotoryさんが開発されているmrubyで書かれたHTTP2サーバーです。
h2oやngx_mruby、mod_mrubyでRack APIベースのAPIに移行(もしくは対応)し、
これを機にmruby-hibariというフレームワークが開発されました。
今回はTrusterdでこのmruby-hibariを動かそうという記事になります。
Rack APIとは
によると、
Rubyにおけるサーバとアプリケーション/フレームワーク間のインターフェースの役割を果たすライブラリ
とのことでした。Perlで言う所の、PSGIということのようでした。
そんなこと言われても何となくわかるだけで、そんな状態では、trusterdをRack APIに
対応するなんてできません。
コードで考えるRack API
幸い、h2oがRack APIに移行した頃、Githubのリポジトリの追っかけやっておいたので、
h2oのテストコードもRack対応の為に変更されており、以下の
Proc.new do |env|
  [200, {"content-type" => "text/plain; charset=utf-8"}, ["hello from h2o_mruby\n"]]
end
というコードを知ることができ、これがRackを理解する大きな手がかりになりました。
サーバー側からは渡されたブロックに対して、Rackが規定する項目をハッシュに詰め込んで
呼べば良いようです。
(さらっと書いていますが、一週間くらい自分は考えようやく理解しました。)
その後、Qiitaに
という素晴らしい記事があり、灯台もと暗しといった感じでした。
この記事のおかげで、サーバ側にどんなものを返せば良いのかがすぐ分かりました。
trusterdとRack APIの対応
規格書を地道に実装するような柄ではないので、動かしてみたい、mruby-hibariに
ターゲットを定め、作業を続けます。
現状のHibariでは
を見れば分かるように
trusterdからブロックに渡す際の引数envに
以下の属性が設定されていないと、何かマズそう気配。
これらの属性のtrusterdでの対応するものを書き出すと
| Rack | trusterd | 
|---|---|
| REQUEST_METHOD | s.method | 
| SCRIPT_NAME | s.filename | 
| PATH_INFO | s.uri | 
| REQUEST_URI | s.unparsed_uri | 
| QUERY_STRING | s.args および、 POST時のパラメータも設定 | 
| SERVER_NAME | s.hostname | 
| SERVER_ADDR | |
| SERVER_PORT | |
| REMOTE_ADDR | s.client_ip | 
| REMOTE_PORT | 
サーバー側のIPアドレスとポート番号も設定ファイルに記述してるから、
参照できるが、Trusterd側のC言語でこれらの属性へのgetterを生やす必要がある。
暫定対応
とにかくhibariが動くか試したいので、/rack/hoge
でカレントディレクトリのhoge.rbを読み込みこれをRack APIで記述された
コードとして実行するような設定ファイルを書きました。
SERVER_NAME = 'Trusterd'
SERVER_VERSION = '0.0.1'
SERVER_DESCRIPTION = "#{SERVER_NAME}/#{SERVER_VERSION}"
root_dir = '.'
def LibTrasterdRackApi(env, filename, s)
  f = File.open(filename, 'r')
  script = f.read
  f.close
  # puts script
  # envをRack APIもどきに設定
  env['REQUEST_METHOD'] = s.method
  env['SCRIPT_NAME'] = s.filename
  env['PATH_INFO'] = s.uri
  env['REQUEST_URI'] = s.unparsed_uri
  env['QUERY_STRING'] = s.args
  env['QUERY_STRING'].slice!(0)
  env['SERVER_NAME'] = s.hostname
  env['SERVER_ADDR'] = '127.0.0.1' # TODO: s.server_host
  env['SERVER_PORT'] = '-1' # TODO s.port
  env['REMOTE_ADDR'] = s.client_ip
  env['REMOTE_PORT'] = '-1' # TODO
  p env['QUERY_STRING']
  # POSTの場合、QUERY_STRINGに格納する
  if (s.method == 'POST')
    if s.args.length < 1
      env['QUERY_STRING'] = s.body
    else
      env['QUERY_STRING'] = env['QUERY_STRING'] + '&' + s.body
    end
  end
  # p env
  ro = eval(script)
  # e={}
  ro.call(env)
end
s = HTTP2::Server.new(
  port: 8080,
  document_root: "#{root_dir}/htdocs",
  server_name: SERVER_DESCRIPTION,
  worker: 'auto',
  # required when tls option is true.
  # tls option is true by default.
  key: "#{root_dir}/ssl/key.pem",
  crt: "#{root_dir}/ssl/cert.pem",
  callback: true,
# connection_record defualt: true
# :connection_record => false,
)
s.set_map_to_storage_cb do
  if s.request.uri =~ /rack\/(.*)/
    s.set_content_cb do
      # puts "/rack/ start" + $1 + ".rb"
      response = LibTrasterdRackApi(s.request_headers.all, Regexp.last_match(1) + '.rb', s)
      # p response
      # s.set_status response[0]
      response[2].each { |line| s.rputs line }
      # s.rputs "rack end"
    end
end
s.run
hibariを使ったサンプル
一応、POSTメッソッドも適当に実装したで、GETとPOSTできるように
以下のサンプルでテスト。
class MyApp < Hibari::App
  def build
    res.code = 200
    res.headers["content-type"] = "text/html; charset=utf8"
    res.body.push("<html><body>")
    res.body.push("<h1>Hello, trusterd!</h1>")
    res.body.push("<hr><form action=\"/rack/hibari?getparam=1\" method=\"post\">")
    res.body.push("<input type=\"text\" name=\"postparam\"><input type=\"submit\"></form>")
    req.params.each do |k,v|
      res.body.push("#{k}: #{v}<br>")
    end
    res.body.push("</body></html>")
  end
end
MyApp.new.run

