はじめに
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