ts_mruby + mruby-mrmagick で画像リサイズするリバースプロキシをサクッと実装してみた
今日は夏コミのようですね!僕は今年は特に原稿を描いておらず用事も無いためコミケに縁のない夏を過ごしてしまいました。
ところで最近拙作の ATS(Apache Traffic Server) プラグインである ts_mruby にレスポンスボディをいじくるメソッドを追加し、ふと思い立って画像リサイズのロジックを mruby で書いてみました。
そのレスポンスボディ加工メソッドは ATS::Filter#transform! になります。 このメソッドではブロックまたは Proc オブジェクトを渡すとブロックパラメータに加工前のレスポンスボディが渡され、評価値でレスポンスボディを差し替えます。
f = ATS::Filter.new f.transform! do |body| # append text to the end of response body body + "rewritted by ts_mruby :D" end
ちなみに ngx_mruby の Nginx::Filter#body
相当のメソッドは実装できていません。また ATS::Filter#transform!
ではブロックの外の変数へのアクセスを許可していません(ブロックの内外で実行時の context を共有していないため)。これらは現状、 ts_mruby を実装する上でフックの挿入をシンプルにして、 mruby スクリプトを書く側に意識させない作りにしているために生じている制限になっています。。。
将来的には 任意のフック時点に挿入可能にして Nginx::Filter#body
相当のメソッドを実装する・・・かもしれません。
話が逸れましたが、 ATS::Filter#transform!
を使えばボディの加工はできます。後は画像ファイル取得時のレスポンスボディを ImageMagick でイイカンジに加工してみます。
今回は ImageMagick の mruby バインディング mruby-mrmagick を使ってみます。
要件としては下記を念頭に置きました。
- ファイルへの読み書きを行わない
- ATS の主要用途はプロキシだしワーカスレッドに極力 I/O させず処理したい
- リクエストのクエリ文字列でリサイズの挙動を変更可能にする
ATS::Filter#transform!
の制約をなんとか突破する
これらの要件を満たす mruby スクリプトを、下記の工夫を織り込んで書いてみました。(もろもろのファイルなどはこちら )
- ImageMagick の blob でボディを加工・取得
- クエリ文字列を
ATS::Request#args
を使って取得 ATS::Filter#transform!
には必要なパラメータを予め Proc#curry で部分適用しておいた Proc オブジェクトを渡す
# parse parameters params = {} req = ATS::Request.new req.args.split('&').each do |arg| key, value = arg.split('=') params[key] = value end # set resizing logic resizer = nil if params.has_key?('scale') resizer = Proc.new do |scale, body| img = Mrmagick::ImageList.new new_img = img.scale(scale) new_img.from_blob(body) new_img.to_blob end.curry.(params['scale'].to_f) end # set filter if params is valid if (resizer) filter = ATS::Filter.new filter.transform! resizer else ATS::rputs 'Parameters are invalid ...' ATS::return ATS::HTTP_BAD_REQUEST end
mruby-mrmagick でラクをしているとは言え、割とシンプルに記述できた気がします!
ちなみに ATS のキャッシュが有効 な場合、 ATS::Filter#transform!
で加工した結果がキャッシュに入ります。
これを活用すれば、このリサイズのロジックもキャッシュヒットしている間は都度走らせずに済み、 CPU リソースの削減が可能かと思います。
・・・ただし、そのためには上記スクリプトにキャッシュヒットしたかどうかのロジックも入れる必要があります(上記ではそれが無いため、キャッシュヒットすると段々とサイズが大きくor小さくなってしまう場合が!)