LoginSignup
86
84

More than 5 years have passed since last update.

rack middlewareをざっくり触ってみた

Last updated at Posted at 2014-09-05

概要

webアプリでとあるパスへのリクエストをフックしてゴニョゴニョする。
そんな方法ないかなぁと思っていたらタイムリーな記事を見つけた。

開発環境でのみ、リクエスト毎になんか処理をフックしたい in Ruby

しかしrack middlewareってどんなもの?状態なので勉強からスタート。

rack middlewareの簡単なお勉強

Rackミドルウェアの作り方を勉強した
ここみたら何となく分かった。

簡単なmiddlewareを書いてみる

/hogeにアクセスが来たら、"hogehoge"を返すmiddleware。
元のアプリに/hogeの処理が定義してあっても、その処理を通さずにクライアントへ結果を返す。
そんなmiddlewareを書いてみた。

my_rack_middleware.rb
class MyRackMiddelware
  def initialize(app)
    @app = app 
  end 

  def call(env)
    if env["PATH_INFO"] == '/hoge'
      res = []
      res[0] = 200 
      res[2] = [ "hogehoge" ]
      content_length = res[2].inject(0) do |sum, content|
        sum + content.bytesize
      end 
      res[1] = { "Content-Type"           => "text/html;charset=utf-8",
                 "Content-Length"         => content_length.to_s,
                 "X-XSS-Protection"       => "1; mode=block",
                 "X-Content-Type-Options" => "nosniff",
                 "X-Frame-Options"        => "SAMEORIGIN"}
    else
      res = @app.call(env)
    end 
    res 
  end 
end

何のことはないinitializeとcallメソッドがあるだけの普通のクラス。

rack middlewareの形式は、

  • リクエストの情報はenvに格納されている (詳細は後述)
  • レスポンスの形式は配列で、[ステータスコード、ヘッダ、ボディ]の順に格納される

ここで使っているenv["PATH_INFO"]にはリクエストパスが格納されているので、これを使って特定のパスへのリクエストをフックしている。
@app.call(env)とすることで、本来呼び出されるWebアプリ(RailsやSinatra)の処理が呼ばれる。
レスポンスの中身を直接指定することで、全く違うページを返したり、リダイレクトさせたり、何かを作成したように見せかけたりできる。

つまり、何でもできてしまう。

(ちなみに、適当なボディだけでレスポンスを返したらContent-Lengthの不一致で怒られた。何でもできるけど、値はきちんと設定すべし。)

envの中身

既に記事を書かれている方がいたのでそちらのリンクを。
HTTP Request Headerがどういう感じでRackのenvに変換されるか

ざっくり言えば、

  • 特定のヘッダはRackが埋めてくれる
  • Rackで定義してないヘッダは、upcaseして、頭に"HTTP_"をつけたものがセットされる

実際にはこんな感じになってる。

env
{"rack.version"=>[1, 2],
 "rack.input"=>#<StringIO:0x00000003a66be0>,
 "rack.errors"=>#<StringIO:0x00000003a66e88>,
 "rack.multithread"=>true,
 "rack.multiprocess"=>true,
 "rack.run_once"=>false,
 "REQUEST_METHOD"=>"POST",
 "SERVER_NAME"=>"example.org",
 "SERVER_PORT"=>"80",
 "QUERY_STRING"=>"",
 "PATH_INFO"=>"/",
 "rack.url_scheme"=>"http",
 "HTTPS"=>"off",
 "SCRIPT_NAME"=>"",
 "CONTENT_LENGTH"=>"40",
 "rack.test"=>true,
 "REMOTE_ADDR"=>"127.0.0.1",
 "CONTENT_TYPE"=>"application/x-www-form-urlencoded",
 "HTTP_HOST"=>"example.org",
 "HTTP_COOKIE"=>""}

postのbody

試している際にpostで送ったbodyが見つからず、???となった。
よく見れば...だったけど、同じハマり方してた人もいたので、一応記載。

env['rack.input']に入ってる。
StringIOオブジェクトで入っているので、getsで取り出す。

# 先ほどのenv
env["rack.input"]       #=> #<StringIO:0x00000003a66be0>
env["rack.input"].gets  #=> "email=test%40example.org%26pass=password"

使い方

  • sinatraの場合 => config.ru に書く
config.ru
# ...
require File.expand_path 'lib/my_rack_middleware', File.dirname(__FILE__)
use MyRackMiddleware
  • railsの場合 => config/environments/xxx.rbやconfig/application.rb に書く
config/environments/development.rb
MyApp::Application.configure do
  # ...
  config.middleware.use MyRackMiddelware
end

テスト

別記事をupしたのでそちらを参照。

86
84
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
86
84