今スイスにいる。
行きの飛行機の中での勉強用にこのページを保存しておいて、Rack middlewareの作り方を学んだ。
第25回 Rackとは何か(3)ミドルウェアのすすめ:Ruby Freaks Lounge|gihyo.jp … 技術評論社
というのもSinatra::RocketIOをRack::RocketIOにしたいからなんだけど、Rack Hijack APIがよくわからない。(hijackについてはそのうち書く)
ソースコードはgithubに全部置いてある。
github.com/shokai/rack-plugin-study
Rack
Sinatra/Rails/Padrino等のRuby製webアプリケーションフレームワークと、webrick/thin/mongrel/unicorn等などのRuby製webサーバーを接続するしくみがRackです。
Webアプリフレームワークが、どのwebサーバーででも動くように接続部分を抽象化してくれている。
以前rackの上にsinatra風のフレームワークを実装してみた時に、くわしく解説した。
→ SinatraっぽいWAFを作る、46行で
rack middleware
rack middlewareを作ると、WebサーバーがWebアプリケーションフレームワーク(WAF)とやりとりするデータを書き換えたりできる。rack::auth系の認証プラグインとか、rack::cache系のキャッシュ系が有名。
どちらもリクエストとレスポンスの間をいじる処理で、プラグインをsinatraやrailsで実装するよりもrackでやったほうが楽だと思った。
rackの実装はほぼ安定していてWAFほどコロコロ変わったりしないし、rack middlewareとして実装しておけばrailsでもsinatraでもpadrinoでも使える。
rack middlewareを作って、sinatraアプリから使えるかどうか試した。
まずSinatraアプリを準備する
まず、単なるHTMLを返すSinatraアプリを作る。文章は感嘆符や句読点が色々欲しかったので、艦これwikiからお借りしました。main.rb
get '/' do '<p><img src="/shokai.jpg"></p> <p><遠征の成功条件(暫定)></p> <p>編成隻数</p> <p>特定の艦種を一定数以上?(条件が無い遠征もある?要検証)</p> <p>旗艦のLv</p> <p>編成合計Lv?(下記参照、要検証) 敵地偵察作戦の場合、軽巡洋艦を1隻含む6隻構成、旗艦Lv20以上、編成合計Lvは駆逐艦のみで構成した場合に100Lv以上。</p> <p>旗艦に軽巡を据える必要性は無く、旗艦:駆逐改でも成功することが確認されている。</p> <p>また、編成艦船の最低Lvは関係無いことも判明している。</p>' end
このmain.rbをconfig.ruからrequireして、rackupすればsinatraアプリが起動する。
config.ru
require 'rack' require 'sinatra' require File.expand_path 'main', File.dirname(__FILE__) run Sinatra::Application
rackup config.ru -p 5000

アプリのレスポンスを書き換えるrack middleware
語尾がクマ語になるrack middlewareを作った。
https://github.com/shokai/rack-plugin-study/tree/master/kuma
こうなる

kuma_response_filter.rb
クラスを作って、initializeとcallだけメソッドを宣言する。# -*- coding: utf-8 -*- class KumaResponseFilter def initialize(app) @app = app end def call(env) res = @app.call env # sinatraに処理させる if res[1]["Content-Type"] =~ /^text\/.+/ res[2] = res[2].map{|body_part| body_part.gsub(/([。!?])/){|s| "クマ#{s}"} } res[1]["Content-Length"] = res[2].map{|body_part| body_part.bytesize }.inject{|a,b| a+b }.to_s end return res end endsinatraに処理させてから、そのレスポンスがtextの時のみ中身をいじってから返している。
感嘆符や句読点の前にクマを挿入するだけ。
Content-Lengthが変わるので最後に計算し直す。
config.ru
use クラス名 するだけでプラグインが有効になって、response bodyが書き換えられる。require 'rack' require 'sinatra' require File.expand_path 'kuma_response_filter', File.dirname(__FILE__) use KumaResponseFilter require File.expand_path 'main', File.dirname(__FILE__) run Sinatra::Application
JPEG画像をglitchしてから配信するrack middleware
https://github.com/shokai/rack-plugin-study/tree/master/image_glitch
JPEG画像がこうなる。glitchはmakimotoさんのスライドを参考にした

image_glitch.rb
Sinatraのレスポンスをみて、image/jpegだったらa/b置換glitchする。module Rack class ImageGlitch def initialize(app) @app = app end def call(env) res = @app.call env # sinatraに処理させる if res[1]["Content-Type"] == "image/jpeg" body = [] res[2].each do |i| body.push i.gsub('a', 'b') end res[2] = body end res end end end
これも、config.ruで
use Rack::ImageGlitchするだけで有効になる。
リクエストがアプリに届く前に修正するrack middleware
https://github.com/shokai/rack-plugin-study/tree/master/jpeg2jpg
試しに、 .jpg を .jpegや.JPEG と打ち間違えてもレスポンスが返ってくるプラグインを作った。

.JpEGとかでリクエストされても、Sinatraのlogには.jpgとしてリクエストされた、と表示される。
127.0.0.1 - - [09/Sep/2013 15:31:26] "GET /shokai.jpg HTTP/1.1" 200 - 0.0024
jpeg_request_filter.rb
PATH_INFO, REQUEST_PATH, REQUEST_URI全部書き換える。# -*- coding: utf-8 -*- class JpegRequestFilter def initialize(app) @app = app end def call(env) # sinatraに処理させる前にrequestを書き換える if env["PATH_INFO"] =~ /.+\.jpeg$/i ["PATH_INFO", "REQUEST_PATH", "REQUEST_URI"].each do |k| env[k].gsub!(/\.jpeg$/i, ".jpg") end end res = @app.call env # sinatraに処理させる end end