LoginSignup
39
15

More than 3 years have passed since last update.

Rack入門 Rack Middleware編 (3/3)

Last updated at Posted at 2020-01-03

前回 は、Rackのプロトコルを理解するために簡単なアプリを作りました。
今回はRackの重要な概念であるRack Middlewareについて学びます。

目次

  1. Rack入門 概念編(1/3)
  2. Rack入門 Rack Application編 (2/3)
  3. [本記事] Rack入門 Rack Middleware編 (3/3)

Rack Middlewareとは

はじめにややこしいことを言いますが、Rackはミドルウェア(Middleware)です。
アプリサーバーとフレームワーク間のやりとりを仲介しているため、ミドルウェアと呼ばれます。

今回学ぶのはミドルウェアとは何か、ではなくてRack Middlewareについてです。
Rackには以下2つの概念があります。

  • Rack Application

    • 前回学んだ、callメソッドを持つオブジェクトのことです
    • StatusCode・Headers・Bodyの3つをレスポンスとして返します
    • Rack Endpointとも呼ばれます
  • Rack Middleware

    • 今回学ぶものです
    • Rack Middlewareはcallメソッドを持つclassである必要があります
    • Rack Applicationと違い、Responseを直接返すのではなく別の処理を呼び出しデータを加工するために使います

Rack Middlewareは、渡ってきたenv情報を加工し、次のmiddlewareまたはendpointに処理を引き渡すものです。

rack03_01.png

Hello Rack Middleware

Rack Middlewareはenv情報を加工し、次に引き渡すために利用します。
Bodyに「Hello Rack Middleware」と追加するだけのRack Middlewareを作成してみます。

class App
  def call(env)
    [200, { "Content-Type" => "text/plain" }, ["HELLO Rack Endpoint!\n\n"]]
  end
end

# Rack Middlewareは以下条件を満たす必要がある
#   - classであること
#   - initializeでappを受け取ること
#   - callメソッドを実装し、Status/Headers/Bodyを返すこと (Rack Endpointと同じ条件)
class HelloRackMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)

    fixed_body = ["Hello Rack Middleware!\n"] + body

    [status, headers, fixed_body]
  end
end

use HelloRackMiddleware
run App.new

Rack Middlewareをclassとして作り、useメソッドを呼び出すことでmiddlewareを追加できます。
アプリを起動し、レスポンスを見てみます。

$ curl http://localhost:9292/

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9292 (#0)
> GET / HTTP/1.1
> Host: localhost:9292
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Transfer-Encoding: chunked
<
Hello Rack Middleware!
HELLO Rack Endpoint!

* Connection #0 to host localhost left intact
* Closing connection 0

Hello Rack Middleware! という文字列がBodyに追加されていますね。

Middlewareはいくつでも追加できます。ただし、useの順序によってMiddlewareの動作順が異なることには注意が必要です。

class App
  def call(env)
    [200, { "Content-Type" => "text/plain" }, ["HELLO Rack Endpoint!\n\n"]]
  end
end

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

  def call(env)
    status, headers, body = @app.call(env)

    fixed_body = ["Hello Rack Middleware!\n"] + body

    [status, headers, fixed_body]
  end
end

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

  def call(env)
    status, headers, body = @app.call(env)

    fixed_body = ["Another Middleware!\n"] + body

    [status, headers, fixed_body]
  end
end

use HelloRackMiddleware
use AnotherMiddleware
run App.new

レスポンスは以下のとおりです。

Hello Rack Middleware!
Another Middleware!
HELLO Rack Endpoint!

Content-Lengthを追加するMiddleware

もう少し実用的なmiddlewareを作ってみましょう。
Response HeaderにContent-Lengthを挿入するmiddlewareを作ります。

class App
  def call(env)
    [200, { "Content-Type" => "text/plain" }, ["HELLO WORLD!", "Hello"]]
  end
end

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

  def call(env)
    status, headers, body = @app.call(env)
    body_size = 0

    body.each do |b|
      body_size += b.bytesize
    end
    headers["Content-Length"] = body_size.to_s

    [status, headers, body]
  end
end

use ContentLengthMiddleware

run App.new

アクセスしてみましょう。

$ curl -v http://localhost:9292/ 

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9292 (#0)
> GET / HTTP/1.1
> Host: localhost:9292
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Length: 17
<
* Connection #0 to host localhost left intact
HELLO WORLD!Hello* Closing connection 0

ちゃんとContent-Lengthが設定されていますね。

Rackにあらかじめ用意されているRack Middleware

Content-Lengthを設定するmiddlewareなど、よく使うと思われるMiddlewareはrack本家で実装されています。
いくつか主要なものを紹介します。

その他のmiddlewareは https://github.com/rack/rack/tree/master/lib/rack を参照してください。

Railsで使われているRack Middleware

Railsもrackのプロトコルに従い作成されています。
Railsで実際に使われているmiddlewareを覗いてみます。

$ rails new racktest

...省略...


$ cd racktest

$ bin/rake middleware

Running via Spring preloader in process 10795
use Webpacker::DevServerProxy
use ActionDispatch::HostAuthorization
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use ActionDispatch::RemoteIp
use Sprockets::Rails::QuietAssets
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use WebConsole::Middleware
use ActionDispatch::DebugExceptions
use ActionDispatch::ActionableExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ContentSecurityPolicy::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Rack::TempfileReaper
run Racktest::Application.routes

いくつかrackで実装されているMiddlewareも利用しているのがわかります。
Railsで利用しているMiddlewareについては、https://guides.rubyonrails.org/rails_on_rack.html#internal-middleware-stack にて詳しく説明されています。

まとめ

  • Rack Middlewareは、渡ってきたリクエスト/レスポンスを加工するために利用する
  • Middlewareはcallメソッドを実装したclassである必要がある

Rackをより理解するためには

全3回にわたって、Rackの基本的な概念を簡単なアプリを作りながら学びました。
思ったよりシンプルな構造でしたね。

よりRackを理解するための参考資料を、以下に記載しておきます。

39
15
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
39
15