とりあえずRackのミドルウェアとして実装すればいいんじゃない?

最近Rackの上にごくごく簡単なフレームワークSinatraも使わず組んでいるんですが, ミドルウェアが便利で, 仕組みもごくごく単純でステキだなぁとか思っているからとりあえずメモしておこうと思って久しぶりにブログを書こうとふにゃらららら.

仕組みから言えばフィルターとして動きます. たとえば:

use Rack::ShowExceptions
use Rack::Lint
run ExampleApp.new

ってミドルウェアを積むと, Rack::ShowExceptions#call(env) -> Rack::Lint#call(env) -> ExampleApp#call(env)と順番に呼ばれます. ただチェインしているだけですね. 素直な実装でボクうれしい.

つーコトでオプション的な機能は引数のoptionsとか設けて対応するんじゃなくて, Rackのミドルウェアに任せれば済むんじゃないか. 素直な実装というコトで, 簡単に作れました. 試しに行頭と行末のホワイトスペースおよび改行を削除するミドルウェアを書いてみた.

require 'rubygems'
require 'rack'

class Strip
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call env
    body, content_length = strip body
    headers['Content-Length'] = content_length.to_s
    [status, headers, body]
  end

  def strip(body)
    content_length = 0
    body.each do |s|
      s.gsub! /^\s+|\s+$|[\r\n]/m, ''
      content_length += Rack::Utils.bytesize s
    end
    [body, content_length]
  end
end

あとはuse StripってしてあげればOK. 簡単だね.

ただミドルウェアを積む順番を意識するのはめんどうなので, @app.call(env)を必ず呼び出して, その結果を加工する程度のモノにした方がよさそうです. 試しにキャッシュ機能とか実装してみたんですけど, キャッシュがあった時点で後方のアプリケーションを呼び出さないので, 順番によって結果が変わる可能性があるからです.

とりあえずミドルウェアで実装しとけばいいんじゃない? って話でした. Sinatraにも使えるしね.