Trusterd(mruby-http2)にリバースプロキシや動的コンテンツ機能を追加した

Trusterd(トラスタード)と呼んでいるmrubyで設定が書けるHTTP/2 Web Serverを、色々と勉強しながら実装しているのですが、それらに下記機能を試験的に追加しました。

  • HTTP/1へのリバースプロキシ機能
  • コンテンツとしてmrubyスクリプトを実行できる機能

これらの機能はまだ実装は甘く、性能・効率面はこれから詰めていきますが、一応動作するという事で簡単に紹介します。Trusterdの設定方法や簡単な紹介は、過去のエントリ「Trusterd: HTTP/2 Web Server scripting with mruby v0.0.1 リリースしました」を御覧ください

HTTP/1へのリバースプロキシ機能

これは、フロントをTrusterdを使ってHTTP/2で受けながら、バックエンドではHTTP/1なWebサーバにリバースプロキシして、そのデータをHTTP/2用に加工してHTTP/2クライアントにレスポンスを返す機能です。

設定は以下のように、set_map_to_strage_cbコールバックに書きます。

s.set_map_to_strage_cb {
   if s.request.uri =~ /^\/upstream(\/.*)/                                       
    s.upstream_uri = $1                                                         
    s.upstream = “http://127.0.0.1“                                             
  end
}

/upstream/index.htmlというURIにアクセスが来たら、/upstream以下に続くURIである/index.htmlを127.0.0.1のサーバに対してリクエストを投げる、という内容になります。

これで実際に、フロントにTrusterdをHTTP/2で立ててバックエンドにApache httpdをHTTP/1で動かしておき、「upsream dayo」と書いただけのupstream.htmlをApache httpd上で配信できるようにしておいて、HTTP/2クライアントであるnghttpでTrusterdに対してリクエストを投げると、以下のようになります。

$ nghttp -v http://127.0.0.1:8080/upstream/upstream.html
[  0.001] send SETTINGS frame <length=15, flags=0x00, stream_id=0>
          (niv=3)
          [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(4):65535]
          [SETTINGS_COMPRESS_DATA(5):1]
[  0.006] send HEADERS frame <length=65, flags=0x05, stream_id=1>
          ; END_STREAM | END_HEADERS
          (padlen=0)
          ; Open new stream
          :authority: 127.0.0.1:8080
          :method: GET
          :path: /upstream/upstream.html
          :scheme: http
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/0.4.0-DEV
[  0.009] recv SETTINGS frame <length=5, flags=0x00, stream_id=0>
          (niv=1)
          [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
[  0.009] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.009] (stream_id=1, noind=0) :status: 200
[  0.009] (stream_id=1, noind=0) date: Tue, 13 May 2014 07:58:44 GMT
[  0.009] (stream_id=1, noind=0) server: Apache/2.4.7 (Ubuntu)
[  0.010] (stream_id=1, noind=0) last-modified: Sun, 11 May 2014 08:13:49 GMT
[  0.010] (stream_id=1, noind=0) etag: “e-4f91b691fc8b7”
[  0.010] (stream_id=1, noind=0) accept-ranges: bytes
[  0.010] (stream_id=1, noind=0) content-length: 14
[  0.010] (stream_id=1, noind=0) content-type: text/html
[  0.010] recv HEADERS frame <length=102, flags=0x04, stream_id=1>
          ; END_HEADERS
          (padlen=0)
          ; First response header
upstream dayo
[  0.011] recv DATA frame <length=14, flags=0x00, stream_id=1>
[  0.011] recv DATA frame <length=0, flags=0x01, stream_id=1>
          ; END_STREAM
[  0.011] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.011] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
          (last_stream_id=0, error_code=NO_ERROR(0), opaque_data(0)=[])

ヘッダ情報をクライアント側から渡したり、リバースプロキシとして返すべきヘッダの整理は未完成ですが、とりあえずHTTP/1のバックエンドサーバが返してくるHeaderやBodyはHTTP/2用にコンバートしてHTTP/2クライアントに返せているのがわかります。HTTP/2で既存のHTTP/1のコンテンツを使えるのはやはり嬉しいものですね。

コンテンツとしてmrubyスクリプトを実行できる機能

続いて、HTTP/2サーバ上でもmrubyスクリプト(mrubyで動くRubyスクリプト)を動的コンテンツとして扱えるようにしました。

この設定は以下のように書きます。

s.set_map_to_strage_cb {
  if s.request.filename =~ /^.*\.rb$/
    s.enable_mruby                     
  end
}

このように書くと、.rbファイルにアクセスがあった場合はmrubyとして実行して、レスポンスを返します。例えばhello worldは以下のように書きます。

まずは、hello.rbを以下のように用意して、

time = Time.now
rputs “Now: #{time}\n”
rputs “hello world”

そして、nghttpでTrusterdにhello.rbに対してアクセスします。

すると以下のようにレスポンスが返って来ます。

$ nghttp -v http://127.0.0.1:8080/hello.rb
[  0.000] send SETTINGS frame <length=15, flags=0x00, stream_id=0>
          (niv=3)
          [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(4):65535]
          [SETTINGS_COMPRESS_DATA(5):1]
[  0.002] send HEADERS frame <length=55, flags=0x05, stream_id=1>
          ; END_STREAM | END_HEADERS
          (padlen=0)
          ; Open new stream
          :authority: 127.0.0.1:8080
          :method: GET
          :path: /hello.rb
          :scheme: http
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/0.4.0-DEV
[  0.011] recv SETTINGS frame <length=5, flags=0x00, stream_id=0>
          (niv=1)
          [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
[  0.011] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.011] (stream_id=1, noind=0) :status: 200
[  0.011] (stream_id=1, noind=0) server: Trusterd/0.0.1
[  0.011] (stream_id=1, noind=0) date: Tue, 13 May 2014 08:21:02 GMT
[  0.011] (stream_id=1, noind=0) last-modified: Tue, 13 May 2014 08:20:58 GMT
[  0.011] recv HEADERS frame <length=57, flags=0x04, stream_id=1>
          ; END_HEADERS
          (padlen=0)
          ; First response header
Now: Tue May 13 01:21:02 2014
hello world
[  0.011] recv DATA frame <length=42, flags=0x00, stream_id=1>
[  0.011] recv DATA frame <length=0, flags=0x01, stream_id=1>
          ; END_STREAM
[  0.011] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.011] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
          (last_stream_id=0, error_code=NO_ERROR(0), opaque_data(0)=[])

時間やhello world文字列のレスポンスが返って来ました。これで一応はTrusterd HTTP/2 Web Serverは動的コンテンツにも対応したことになりますね!

このあたりの実装も、まずは動く事重視で最適化はまだきちんとされていないので、これから詰めていこうと思います。

まとめ

以上のように、最近子育てをしながら合間をみつけてやっていたTrusterdの実装の進捗を書いてみました。まだまだやることや考える事、さらには実装において勉強しないといけない事は沢山ですが、実装するにつれ今まで分かっていなかった事がわかったりと、とても勉強になっています。思考錯誤しながら実装しており、コードもいったりきたりな内容になってきていますが、そのあたりも整理しながら実装していきたいです。

もし興味を持った方がおられたら、一緒にTrusterdを色々実装しながら勉強しつつ、最終的にはTrusterdをそれなりに良いHTTP/2 Web Serverにしてみませんか?

どうぞよろしくお願いします。

Leave a Comment