mrubyのHTTP2サーバーのTrusterdでRackベースのフレームワークを動かす

# はじめに

Trusterdはこのアドベントカレンダーの主催者の@matsumotoryさんが開発されているmrubyで書かれたHTTP2サーバーです。

h2oやngx_mruby、mod_mrubyでRack APIベースのAPIに移行（もしくは対応）し、
これを機にmruby-hibariというフレームワークが開発されました。

今回はTrusterdでこのmruby-hibariを動かそうという記事になります。

# Rack APIとは

- [第23回　Rackとは何か（1）Rackの生まれた背景：Ruby Freaks Lounge｜gihyo.jp … 技術評論社](http://gihyo.jp/dev/serial/01/ruby/0023)

によると、 

Rubyにおけるサーバとアプリケーション／フレームワーク間のインターフェースの役割を果たすライブラリ

とのことでした。Perlで言う所の、PSGIということのようでした。

そんなこと言われても何となくわかるだけで、そんな状態では、trusterdをRack APIに
対応するなんてできません。

## コードで考えるRack API

幸い、h2oがRack APIに移行した頃、Githubのリポジトリの追っかけやっておいたので、
h2oのテストコードもRack対応の為に変更されており、以下の

```r
Proc.new do |env|
  [200, {"content-type" => "text/plain; charset=utf-8"}, ["hello from h2o_mruby\n"]]
end
```

というコードを知ることができ、これがRackを理解する大きな手がかりになりました。

サーバー側からは渡されたブロックに対して、Rackが規定する項目をハッシュに詰め込んで
呼べば良いようです。
（さらっと書いていますが、一週間くらい自分は考えようやく理解しました。）

その後、Qiitaに

- [Ruby - Rack解説 - Rackの構造とRack DSL - Qiita](http://qiita.com/higuma/items/838f4f58bc4a0645950a)

という素晴らしい記事があり、灯台もと暗しといった感じでした。

この記事のおかげで、サーバ側にどんなものを返せば良いのかがすぐ分かりました。

# trusterdとRack APIの対応

規格書を地道に実装するような柄ではないので、動かしてみたい、mruby-hibariに
ターゲットを定め、作業を続けます。

現状のHibariでは

- https://github.com/kentaro/mruby-hibari/blob/master/mrblib/hibari/request.rb#L26-L41

を見れば分かるように
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で記述された
コードとして実行するような設定ファイルを書きました。

```rb
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できるように
以下のサンプルでテスト。


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

# 動いた

![](https://j.gifs.com/gJ54oD.gif)