Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
83
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

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

概要

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したのでそちらを参照。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
83
Help us understand the problem. What are the problem?