Rack xử lý một HTTP request như thế nào?

Khi mới học Rails, Rack thường xuất hiện trong Gemfile như một người họ hàng ít nói: luôn có mặt nhưng chẳng ai giải thích. Thực ra contract của Rack rất nhỏ.

Một Rack application là object respond to call và nhận env:

1
2
3
4
5
6
7
8
9
class App
def call(env)
[
200,
{ "content-type" => "text/plain; charset=utf-8" },
["Hello from Rack\n"]
]
end
end

Response có ba phần:

  1. HTTP status integer.
  2. Headers dạng hash.
  3. Body là object có thể each và yield string.

Chạy application

1
2
3
4
# config.ru
require_relative "app"

run App.new
1
bundle exec rackup

Rack adapter nhận request từ web server, xây env, gọi application và chuyển tuple response trở lại HTTP. Rack không dịch “thứ app server hiểu” sang “thứ Rails hiểu” theo kiểu phép thuật; nó định nghĩa interface chung để server và framework nói cùng protocol.

Đọc request

Đừng tự parse QUERY_STRING hoặc eval dữ liệu từ file/request. eval biến input thành Ruby code—một feature khá mạnh nếu mục tiêu là trao server cho người lạ.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require "rack"
require "json"

class App
def call(env)
request = Rack::Request.new(env)

payload = {
method: request.request_method,
path: request.path,
name: request.params.fetch("name", "anonymous")
}

[
200,
{ "content-type" => "application/json" },
[JSON.generate(payload)]
]
end
end

Middleware

Middleware cũng là Rack app, nhưng giữ reference tới app tiếp theo:

1
2
3
4
5
6
7
8
9
10
11
12
13
class RequestTimer
def initialize(app)
@app = app
end

def call(env)
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
status, headers, body = @app.call(env)
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
headers["server-timing"] = "app;dur=#{(elapsed * 1000).round(2)}"
[status, headers, body]
end
end
1
2
3
# config.ru
use RequestTimer
run App.new

Rails request đi qua một stack middleware cho logging, cookies, sessions, exceptions, security headers và nhiều việc khác trước khi tới controller.

Rack đáng học không phải vì ta sẽ bỏ Rails để tự viết framework vào cuối tuần. Nó cho thấy boundary thật sự: env vào, response ba phần ra, middleware bao quanh. Phần còn lại là abstraction—rất hữu ích, nhưng không còn là phép màu.

References