เกิดอะไรขึ้นเบื้องหลังของ Rails, Sinatra และ Ruby web frameworks อื่นๆ ทุกตัว?
คำตอบคือ Rack, key component ที่ทำให้เป็นไปได้
แต Rack คืออะไรแน่?
Rack เป็น layer ระหว่าง framework (Rails) และ application server (Puma)

มันคือกาวที่ให้พวกมัน communicate ได้
ทำไมเราใช้ Rack?
เราใช้ Rack เพราะมันทำให้ frameworks และ servers ต่างๆ สลับกันได้
พวกมันกลายเป็น components ที่ plug-in ได้
นี่หมายถึงคุณใช้ Puma กับ Rails, Sinatra และ Rack-compatible frameworks อื่นๆ ได้ ไม่สำคัญ framework หรือ server ไหน ถ้าพวกมัน implement Rack interface
ด้วย Rack ทุก component ทำหน้าที่ของมัน และทุกคนมีความสุข!
Rack Middleware คืออะไร?
Rack นั่งกลางของทุก web request และ response
ผลคือ มัน act as guardian โดย deny access unwanted requests หรือ act as historian โดย track slow responses
นั่นคือ Rack middleware!
Ruby programs เล็กๆ ที่ถูกเรียกเป็นส่วนหนึ่งของ request-response cycle และได้โอกาสทำอะไรกับมัน
ใช้ทำอะไรบ้าง?
- Logging
- Sessions
- Profiling (หาว่า request ใช้เวลานานแค่ไหน)
- Caching
- Security (deny requests by IP, หรือ limit # requests)
- Serving static files (css, js, png…)
สิ่งเหล่านี้ useful มาก และ Rails ใช้ middleware ทำให้ฟีเจอร์บางอย่างทำงานได้
คุณดูรายการ middleware ด้วย rake middleware ใน Rails project
ตอนนี้ Rack interface ที่กล่าวไป
มัน长什么?
มา show ด้วยตัวอย่าง…
วิธีเขียน Rack Application เอง
คุณเรียนรู้ Rack ทำงานอย่างไรโดยเขียน application เอง
มาทำกัน!
Rack application คือคลาสที่มีหนึ่งเมธอด: call
长这样:
require 'rack'
handler = Rack::Handler::Thin
class RackApp
def call(env)
[200, {"Content-Type" => "text/plain"}, "Hello from Rack"]
end
end
handler.run RackApp.new
โค้ดนี้จะเริ่ม server บน port 8080 (ลองดู!)
อาร์เรย์ที่คืนคืออะไร?
- HTTP status code (200)
- HTTP headers ("Content-Type")
- Contents ("Hello from Rack")
หากอยากเข้าถึง request details ใช้ argument env
แบบนี้:
req = Rack::Request.new(env)
เมธอดเหล่านี้พร้อมใช้:
- path_info (/articles/1)
- ip (ของ user)
- user_agent (Chrome, Firefox, Safari…)
- request_method (get / post)
- body (contents)
- media_type (plain, json, html)
คุณใช้ข้อมูลนี้ สร้าง Rack application ของคุณ
เช่น เรา deny access เนื้อหา หาก IP address เป็น 5.5.5.5
โค้ด:
require 'rack'
handler = Rack::Handler::Thin
class RackApp
def call(env)
req = Rack::Request.new(env)
if req.ip == "5.5.5.5"
[403, {}, ""]
else
[200, {"Content-Type" => "text/plain"}, "Hello from Rack"]
end
end
end
handler.run RackApp.new
คุณเปลี่ยน address เป็น 127.0.0.1 หากอยากเห็น effect
หากไม่ได้ ลอง ::1 IPv6 version ของ localhost
วิธีเขียนและใช้ Rack Middleware
ตอนนี้:
คุณ chain application และ middleware ให้ทำงานร่วมกันอย่างไร?
ใช้ Rack::Builder
วิธีเข้าใจดีที่สุดคือตัวอย่าง
นี่คือ Rack app:
require 'rack'
handler = Rack::Handler::Thin
class RackApp
def call(env)
req = Rack::Request.new(env)
[200, {"Content-Type" => "text/plain"}, "Hello from Rack - #{req.ip}"]
end
end
นี่คือ middleware:
class FilterLocalHost
def initialize(app)
@app = app
end
def call(env)
req = Rack::Request.new(env)
if req.ip == "127.0.0.1" || req.ip == "::1"
[403, {}, ""]
else
@app.call(env)
end
end
end
นี่คือวิธีรวมกัน:
app = Rack::Builder.new do |builder| builder.use FilterLocalHost builder.run RackApp.new end handler.run app
ในตัวอย่างนี้ มีสอง Rack applications:
- หนึ่งสำหรับ IP check (
FilterLocalHost) - หนึ่งสำหรับ application เองส่ง content (HTML, JSON ฯลฯ)
สังเกต @app.call(env) นั่นคือสิ่งที่ทำให้ FilterLocalHost เป็น middleware
หนึ่งในสองสิ่งเกิดขึ้น:
- เราคืน response ซึ่งหยุด middleware chain
- เราส่ง request ต่อด้วย
@app.call(env)ไป middleware ถัดไป หรือ app เอง
ภายในทุกส่วนของ Rack app รวม middleware คุณเปลี่ยน response ได้
ตัวอย่าง:
class UpcaseAll
def initialize(app)
@app = app
end
def call(env)
status, headers, response = @app.call(env)
response.upcase!
[status, headers, response]
end
end
นั่นคือ Rack ทำงานอย่างแน่นอน 🙂
สรุป
คุณเรียนรู้เกี่ยวกับ Rack interface ที่ขับเคลื่อน interaction ระหว่าง Ruby web frameworks และ servers คุณยังเรียนรู้วิธีเขียน Rack application เองเพื่อเข้าใจการทำงาน
หากมีคำถามหรือ feedback ยินดีรับฟังใน comments ด้านล่าง
ขอบคุณที่อ่าน!