trusterd HTTP/2 Web Serverは、設定や機能の拡張をRuby(mruby)で容易に実装できるという特徴を持つミドルウェアです。
また、別のアプリケーションにtrusterdのサーバ機能やクライアント機能を組み込めるという特徴も持っていますが、これについては以前記事で紹介しました。
C言語のアプリにmruby経由でtrusterdのHTTP/2サーバ機能を5分で組み込む方法 - 人間とウェブの未来
trusterdのHTTP/2クライアント機能をCアプリに組み込んでみよう - 人間とウェブの未来
今日は、trusterdの設定や機能拡張のためのコールバックの書き方を簡単に紹介します。
まずは基本設定を書く
trusterdの基本設定は以下のようになります。
SERVER_NAME = "Trusterd" SERVER_VERSION = "0.0.1" SERVER_DESCRIPTION = "#{SERVER_NAME}/#{SERVER_VERSION}" root_dir = "/usr/local/trusterd" s = HTTP2::Server.new({ :port => 8080, :document_root => "#{root_dir}/htdocs", :server_name => SERVER_DESCRIPTION, :run_user => "daemon", :tls => false, :callback => true, :worker => "auto", })
各種パラメータの意味はREADMEや設定サンプルを御覧ください。基本的にはこの設定で、s.run
すればサーバは起動するのですが、今回は引き続きコールバックの設定を記述してみます。そのために、:callback => true
が必要になります。デフォルトはfalseです。
set_map_to_strage_cb
:URLとファイルのマッピングでコールバック
Webサーバにおいて、アクセスのあったURLとサーバ内のファイルを紐付ける処理があります。その処理のフェイズで、任意のマッピング処理を書いてみます。
s.set_map_to_strage_cb do # .phpにアクセスがあったら全てindex.htmlにマッピング s.location ".*\.php$" do s.filename = s.document_root + "/index.html" end # /helloにアクセスがあったらcontentコールバックを設定(後述) s.location "\/hello$" do # set content handler phase s.set_content_cb do s.echo "hello #{s.request_headers["user-agent"]} from #{s.conn.client_ip}, welcome to trusterd" end end end
上記のように処理を書くことができます。内容はコメントの通りです。ここで、2つめのマッピング処理で利用しているset_content_cb
は、レスポンス生成時にコールバックすることで、レスポンスを独自で生成することができます。set_content_cb
はset_map_to_strage_cb
コールバックの外でも定義できますが、使い方としてはマッピング時に利用することが多いと思います。
set_content_cb
:レスポンス生成時にコールバック
上述のset_map_to_strage
で説明しましたが、レスポンス生成時に拡張でレスポンスボディを生成したい場合等に使用します。echo
やrputs
メソッドによってレスポンス出力できます。
set_access_checker_cb
:URLマッピング後のアクセスチェックでコールバック
続いて、URLのマッピング後に実際にレスポンスを返していよいかどうかを判定し、アクセスを制御するためのコールバック処理を書いてみます。その場合はset_access_checker_cb
を使います。
s.set_access_checker_cb do s.file "#{s.document_root}/index.cgi" do s.set_status 403 end end
上記の処理では、index.cgiというファイルが要求されたら、問答無用でFORBIDDENを返すという処理です。簡単ですね。ここでベーシック認証やRedisを使った様々な認証を書くことができるでしょう。
set_fixups_cb
:レスポンス送信の直前でコールバック
レスポンヘッダーやレスポンスボディがほぼ確定し、後はレスポンスを送信するだけというタイミングでset_fixups_cb
を使うと処理をコールバックできます。例えば、生成済みのレスポンスヘッダーをさらに上書きしたり追加したりすることもこのコールバックで処理できます。
s.set_fixups_cb do # lastヘッダを追加 s.response_headers["last"] = "OK" # serverヘッダをアップデート s.response_headers["server"] = "change_server" # リクエストボディをヘッダに追加 if ! s.body.nil? s.response_headers["post-data"] = s.body end end
上記のコメントのように、ヘッダの追加や更新ができます。また、唐突にリクエストボディが出てきましたが、これもs.body
によって取得することができるので、ここで使うのではなくもっと早い段階でリクエストボディを利用することもできます。
set_logging_cb
:ログ生成時にコールバック
レスポンスを送った後のログ生成時にset_logging_cb
を使うと処理をコールバックできます。今回は予め容易してるアクセスログのためのメソッドを使ってみます。まずはコールバックする前のsetup処理をコールバックの外に書きます。
s.setup_access_log({ :file => "#{root_dir}/logs/access.log", :format => :default, :type => :plain, })
このメソッドにより、アクセスログ出力先ファイルやフォーマット、出力タイプ等を指定します。:format
には:default
と:custom
、:type
には:plain
や:json
が指定できます。
これを書いて上で、ログ生成時のコールバックを記述します。
s.set_logging_cb do s.write_access_log end
今回は簡単のためにログ出力するだけの処理をコールバックします。この状態でログを出力すると、
127.0.0.1 - - [Wed, 18 Feb 2015 03:47:01 GMT] "GET /index.html HTTP/2" 200 22 "-" ""
と出力されます。また、
s.setup_access_log({ :file => "#{root_dir}/logs/access.log", :format => :default, :type => :json, })
として、json出力の場合は、
{"ip":"127.0.0.1","date":"Wed, 18 Feb 2015 05:09:54 GMT","scheme":"http","mehtod":"GET","status":200,"content_length":22,"uri":"/index.html","filename":"/usr/local/trusterd/htdocs/index.html","user_agent":"nghttp2/0.7.5-DEV"}
のように出力されます。
:custom
を指定した場合は、コールバック内のwrite_access_log
にログ出力を引数で渡します。例えば、
s.set_logging_cb do log = { :ip => s.conn.client_ip, :date => s.date, :scheme => s.request_headers[":scheme"], :mehtod => s.request_headers[":method"], :status => s.status, :content_length => s.content_length, :uri => s.uri, :filename => s.filename, :user_agent => s.user_agent, } s.write_access_log JSON.stringify(log) + "\n" end
等と書くことができます。これは、:default
の:json
フォーマットと同様です。
まとめ
今回はtrusterdの基本的な設定とコールバックを使った簡単な機能拡張の例を紹介しました。息子が泣くので今は抱っこしながら片手で書いているので、まとめはこの辺りで...
今日紹介した設定はここにもありますので参考まで。
trusterd/trusterd.conf.callbacks.example.rb at master · trusterd/trusterd · GitHub
その他、サーバを起動しつつクライアントから情報を取るというような良くわからない設定も以下のように書くことができますので、色々遊べると思います。
SERVER_NAME = "Trusterd" SERVER_VERSION = "0.0.1" SERVER_DESCRIPTION = "#{SERVER_NAME}/#{SERVER_VERSION}" root_dir = "/usr/local/trusterd" run_user = "matsumotory" new_config = JSON::stringify({:port => 8003, :worker => 3}) tls = HTTP2::Server.new({ :port => 8080, :document_root => "#{root_dir}/htdocs", :server_name => SERVER_DESCRIPTION, :worker => "auto", :run_user => run_user, :rlimit_nofile => 65535, :write_packet_buffer_expand_size => 4096, :write_packet_buffer_limit_size => 4096, :key => "#{root_dir}/ssl/server.key", :crt => "#{root_dir}/ssl/server.crt", :callback => true, }) tls.set_content_cb { if tls.r.uri =~ /\/config\// tls.r.rputs new_config end } pid1 = Process.fork() { tls.run } sleep 2 config = JSON::parse(HTTP2::Client.get("https://127.0.0.1:8080/config/").body) puts "get new_port=#{config['port']} and n_worker=#{config['worker']}" no_tls = HTTP2::Server.new({ :port => config['port'], :document_root => "#{root_dir}/htdocs", :server_name => SERVER_DESCRIPTION + "no_tls", :worker => config['worker'], :run_user => run_user, :tls => false, }) pid2 = Process.fork() { no_tls.run } Process.waitpid pid1 Process.waitpid pid2