Rack middleware trong Ruby là gì? Tôi không thể tìm thấy bất kỳ lời giải thích tốt cho những gì họ có nghĩa là "phần mềm trung gian".
Rack middleware trong Ruby là gì? Tôi không thể tìm thấy bất kỳ lời giải thích tốt cho những gì họ có nghĩa là "phần mềm trung gian".
Câu trả lời:
Phần mềm trung gian của Rack không chỉ là "cách lọc yêu cầu và phản hồi" - đó là cách triển khai mẫu thiết kế đường ống cho các máy chủ web sử dụng Rack .
Nó rất rõ ràng tách ra các giai đoạn khác nhau để xử lý một yêu cầu - tách các mối quan tâm là mục tiêu chính của tất cả các sản phẩm phần mềm được thiết kế tốt.
Ví dụ với Rack tôi có thể có các giai đoạn riêng biệt của đường ống đang thực hiện:
Xác thực : khi yêu cầu đến, các chi tiết đăng nhập của người dùng có chính xác không? Làm cách nào để xác thực OAuth, Xác thực cơ bản HTTP, tên / mật khẩu này?
Ủy quyền : "người dùng có được phép thực hiện tác vụ cụ thể này không?", Tức là bảo mật dựa trên vai trò.
Bộ nhớ đệm : Tôi đã xử lý yêu cầu này rồi, tôi có thể trả lại kết quả đã lưu không?
Trang trí : làm thế nào tôi có thể tăng cường yêu cầu để làm cho xử lý hạ nguồn tốt hơn?
Giám sát hiệu suất và sử dụng : tôi có thể nhận được số liệu thống kê nào từ yêu cầu và phản hồi?
Thực thi : thực sự xử lý yêu cầu và cung cấp một phản hồi.
Có thể tách các giai đoạn khác nhau (và tùy ý bao gồm chúng) là một trợ giúp tuyệt vời trong việc phát triển các ứng dụng có cấu trúc tốt.
Ngoài ra còn có một hệ sinh thái tuyệt vời phát triển xung quanh Rack Middleware - bạn sẽ có thể tìm thấy các thành phần giá đỡ được xây dựng sẵn để thực hiện tất cả các bước trên và hơn thế nữa. Xem wiki Rack GitHub để biết danh sách các phần mềm trung gian .
Middleware là một thuật ngữ khủng khiếp dùng để chỉ bất kỳ thành phần / thư viện phần mềm nào hỗ trợ nhưng không liên quan trực tiếp đến việc thực hiện một số tác vụ. Các ví dụ rất phổ biến là ghi nhật ký, xác thực và các thành phần xử lý ngang phổ biến khác . Đây có thể là những thứ mà mọi người cần trên nhiều ứng dụng nhưng không có quá nhiều người quan tâm (hoặc nên có) trong việc xây dựng bản thân.
Nhận xét về nó là một cách để lọc các yêu cầu có lẽ đến từ RailsCast tập 151: Diễn viên màn hình Rack Middleware .
Phần mềm trung gian Rack phát triển ra khỏi Rack và có một phần giới thiệu tuyệt vời tại Giới thiệu về phần mềm trung gian Rack .
Có phần giới thiệu về phần mềm trung gian trên Wikipedia ở đây .
Trước hết, Rack chính xác là hai điều:
Rack - Giao diện máy chủ web
Những điều cơ bản của rack là một quy ước đơn giản. Mỗi máy chủ web tuân thủ giá sẽ luôn gọi một phương thức gọi trên một đối tượng bạn đưa cho anh ta và phục vụ kết quả của phương thức đó. Rack chỉ định chính xác cách thức phương thức cuộc gọi này trông như thế nào và nó phải trả về cái gì. Đó là giá đỡ.
Hãy thử một cách đơn giản. Tôi sẽ sử dụng WEBrick làm máy chủ web tuân thủ rack, nhưng bất kỳ ai trong số họ cũng sẽ làm được. Hãy tạo một ứng dụng web đơn giản trả về chuỗi JSON. Để làm điều này, chúng tôi sẽ tạo một tệp có tên config.ru. Các config.ru sẽ tự động được gọi bởi rackup của rack gem, đơn giản là sẽ chạy các nội dung của config.ru trong một máy chủ web tuân thủ rack. Vì vậy, hãy thêm phần sau vào tệp config.ru:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
map '/hello.json' do
run JSONServer.new
end
Vì quy ước chỉ định máy chủ của chúng tôi có một phương thức gọi là cuộc gọi chấp nhận hàm băm môi trường và trả về một mảng có dạng [trạng thái, tiêu đề, nội dung] để máy chủ web phục vụ. Hãy thử nó bằng cách gọi rackup. Một máy chủ tuân thủ giá mặc định, có thể WEBrick hoặc Mongrel sẽ khởi động và ngay lập tức chờ yêu cầu phục vụ.
$ rackup
[2012-02-19 22:39:26] INFO WEBrick 1.3.1
[2012-02-19 22:39:26] INFO ruby 1.9.3 (2012-01-17) [x86_64-darwin11.2.0]
[2012-02-19 22:39:26] INFO WEBrick::HTTPServer#start: pid=16121 port=9292
Hãy kiểm tra máy chủ JSON mới của chúng tôi bằng cách cuộn tròn hoặc truy cập url http://localhost:9292/hello.json
và voila:
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Nó hoạt động. Tuyệt quá! Đó là nền tảng cho mọi khung web, có thể là Rails hoặc Sinatra. Tại một số điểm, họ thực hiện một phương thức gọi, làm việc thông qua tất cả các mã khung và cuối cùng trả về một phản hồi ở dạng [trạng thái, tiêu đề, nội dung] điển hình.
Ví dụ, trong Ruby on Rails, các yêu cầu rack chạm vào ActionDispatch::Routing.Mapper
lớp trông như thế này:
module ActionDispatch
module Routing
class Mapper
...
def initialize(app, constraints, request)
@app, @constraints, @request = app, constraints, request
end
def matches?(env)
req = @request.new(env)
...
return true
end
def call(env)
matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
end
...
end
end
Vì vậy, về cơ bản Rails kiểm tra, phụ thuộc vào hàm băm env nếu bất kỳ tuyến nào khớp. Nếu vậy, nó chuyển hàm băm env cho ứng dụng để tính toán phản hồi, nếu không, nó sẽ phản hồi ngay lập tức với 404. Vì vậy, bất kỳ máy chủ web nào tuân thủ quy ước giao diện rack, đều có thể phục vụ ứng dụng Rails hoàn toàn.
Middleware
Rack cũng hỗ trợ việc tạo các lớp trung gian. Về cơ bản họ chặn một yêu cầu, làm một cái gì đó với nó và chuyển nó đi. Điều này rất hữu ích cho các nhiệm vụ linh hoạt.
Giả sử chúng tôi muốn thêm ghi nhật ký vào máy chủ JSON của chúng tôi để đo thời gian yêu cầu. Chúng ta chỉ cần tạo một logger phần mềm trung gian thực hiện chính xác điều này:
class RackLogger
def initialize(app)
@app = app
end
def call(env)
@start = Time.now
@status, @headers, @body = @app.call(env)
@duration = ((Time.now - @start).to_f * 1000).round(2)
puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms"
[@status, @headers, @body]
end
end
Khi được tạo, nó sẽ tự lưu một bản sao của ứng dụng rack thực tế. Trong trường hợp của chúng tôi, đó là một ví dụ của Máy chủ JSONS của chúng tôi. Rack tự động gọi phương thức gọi trên phần mềm trung gian và mong đợi trở lại một [status, headers, body]
mảng, giống như trả về JSONServer của chúng tôi.
Vì vậy, trong phần mềm trung gian này, điểm bắt đầu được thực hiện, sau đó cuộc gọi thực tế đến Máy chủ JSONS được thực hiện @app.call(env)
, sau đó trình ghi nhật ký xuất mục nhập nhật ký và cuối cùng trả về phản hồi là [@status, @headers, @body]
.
Để làm cho rackup.ru nhỏ của chúng tôi sử dụng phần mềm trung gian này, hãy thêm sử dụng RackLogger vào nó như thế này:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
class RackLogger
def initialize(app)
@app = app
end
def call(env)
@start = Time.now
@status, @headers, @body = @app.call(env)
@duration = ((Time.now - @start).to_f * 1000).round(2)
puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms"
[@status, @headers, @body]
end
end
use RackLogger
map '/hello.json' do
run JSONServer.new
end
Khởi động lại máy chủ và voila, nó xuất ra một bản ghi trên mỗi yêu cầu. Rack cho phép bạn thêm nhiều phần mềm trung gian được gọi theo thứ tự chúng được thêm vào. Đó chỉ là một cách tuyệt vời để thêm chức năng mà không thay đổi lõi của ứng dụng rack.
Giá đỡ - Đá quý
Mặc dù giá đỡ - trước hết - là một quy ước, nó cũng là một viên đá quý cung cấp chức năng tuyệt vời. Một trong số chúng tôi đã sử dụng cho máy chủ JSON của mình, lệnh rackup. Nhưng còn nhiều hơn thế! Giá đỡ cung cấp các ứng dụng nhỏ cho nhiều trường hợp sử dụng, như phục vụ các tệp tĩnh hoặc thậm chí toàn bộ thư mục. Hãy xem cách chúng tôi phục vụ một tệp đơn giản, ví dụ: tệp HTML rất cơ bản có tại htmls / index.html:
<!DOCTYPE HTML>
<html>
<head>
<title>The Index</title>
</head>
<body>
<p>Index Page</p>
</body>
</html>
Chúng tôi có thể muốn phân phát tệp này từ thư mục gốc của trang web, vì vậy hãy thêm phần sau vào config.ru:
map '/' do
run Rack::File.new "htmls/index.html"
end
Nếu chúng tôi truy cập, http://localhost:9292
chúng tôi thấy tệp html của chúng tôi được hiển thị hoàn hảo. Điều đó thật dễ dàng phải không?
Chúng ta hãy thêm toàn bộ thư mục các tệp javascript bằng cách tạo một số tệp javascript trong / javascripts và thêm phần sau vào config.ru:
map '/javascripts' do
run Rack::Directory.new "javascripts"
end
Khởi động lại máy chủ và truy cập http://localhost:9292/javascript
và bạn sẽ thấy một danh sách tất cả các tệp javascript mà bạn có thể bao gồm ngay bây giờ từ bất cứ đâu.
Tôi đã có một vấn đề hiểu bản thân Rack trong một khoảng thời gian tốt. Tôi chỉ hoàn toàn hiểu nó sau khi tự mình làm máy chủ web Ruby thu nhỏ này . Tôi đã chia sẻ những kiến thức của mình về Rack (dưới dạng một câu chuyện) tại đây trên blog của mình: http : //gaurav Touche.com/what-is-rack-in-ruby-rails
Phản hồi được chào đón nhiều hơn.
config.ru
ví dụ runnable tối thiểu
app = Proc.new do |env|
[
200,
{
'Content-Type' => 'text/plain'
},
["main\n"]
]
end
class Middleware
def initialize(app)
@app = app
end
def call(env)
@status, @headers, @body = @app.call(env)
[@status, @headers, @body << "Middleware\n"]
end
end
use(Middleware)
run(app)
Chạy rackup
và tham quan localhost:9292
. Đầu ra là:
main
Middleware
Vì vậy, rõ ràng là Middleware
kết thúc tốt đẹp và gọi ứng dụng chính. Do đó, nó có thể xử lý trước yêu cầu và xử lý sau phản hồi theo bất kỳ cách nào.
Như đã giải thích tại: http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack , Rails sử dụng phần mềm trung gian Rack cho rất nhiều chức năng của nó và bạn có thể thêm bạn sở hữu config.middleware.use
phương thức gia đình.
Ưu điểm của việc triển khai chức năng trong một phần mềm trung gian là bạn có thể sử dụng lại nó trên bất kỳ khung Rack nào, do đó, tất cả các Ruby chính, và không chỉ Rails.
Rack middleware là một cách để lọc một yêu cầu và phản hồi đến ứng dụng của bạn. Một thành phần phần mềm trung gian nằm giữa máy khách và máy chủ, xử lý các yêu cầu gửi đến và phản hồi ra ngoài, nhưng nó không chỉ là giao diện có thể được sử dụng để nói chuyện với máy chủ web. Nó được sử dụng để nhóm và sắp xếp các mô-đun, thường là các lớp Ruby và chỉ định sự phụ thuộc giữa chúng. Mô-đun phần mềm trung gian giá chỉ phải: - có hàm tạo lấy ứng dụng tiếp theo trong ngăn xếp làm tham số - đáp ứng với phương thức gọi cuộc gọi, lấy hàm băm môi trường làm tham số. Trả về giá trị từ cuộc gọi này là một mảng gồm: mã trạng thái, hàm băm môi trường và phần thân phản hồi.
Tôi đã sử dụng phần mềm trung gian Rack để giải quyết một số vấn đề:
Nó có khả năng sửa chữa khá thanh lịch trong cả hai trường hợp.
Rack cung cấp một giao diện tối thiểu giữa các máy chủ web hỗ trợ các khung Ruby và Ruby.
Sử dụng Rack bạn có thể viết một ứng dụng Rack.
Rack sẽ chuyển băm Môi trường (một Hash, chứa trong yêu cầu HTTP từ máy khách, bao gồm các tiêu đề giống CGI) cho Ứng dụng Rack của bạn, có thể sử dụng những thứ có trong hàm băm này để làm bất cứ điều gì nó muốn.
Để sử dụng Rack, bạn phải cung cấp 'ứng dụng' - một đối tượng phản hồi #call
phương thức với Môi trường Hash dưới dạng tham số (thường được xác định là env
). #call
phải trả về một mảng có đúng ba giá trị:
each
).Bạn có thể viết Ứng dụng Rack trả về một mảng như vậy - điều này sẽ được gửi lại cho khách hàng của bạn, bởi Rack, bên trong Phản hồi (đây thực sự sẽ là một phiên bản của Class Rack::Response
[nhấp để đi đến tài liệu]).
gem install rack
config.ru
tập tin - Rack biết để tìm kiếm điều này.Chúng tôi sẽ tạo một Ứng dụng Rack nhỏ trả về Phản hồi (ví dụ Rack::Response
), Người phản hồi là một mảng có chứa Chuỗi : "Hello, World!"
.
Chúng tôi sẽ kích hoạt một máy chủ cục bộ bằng cách sử dụng lệnh rackup
.
Khi truy cập cổng có liên quan trong trình duyệt của chúng tôi, chúng tôi sẽ thấy "Xin chào, Thế giới!" được hiển thị trong khung nhìn.
#./message_app.rb
class MessageApp
def call(env)
[200, {}, ['Hello, World!']]
end
end
#./config.ru
require_relative './message_app'
run MessageApp.new
Khởi động một máy chủ cục bộ với rackup
và truy cập localhost: 9292 và bạn sẽ thấy 'Xin chào, Thế giới!' kết xuất.
Đây không phải là một lời giải thích toàn diện, nhưng về cơ bản, điều xảy ra ở đây là Máy khách (trình duyệt) gửi Yêu cầu HTTP đến Rack, thông qua máy chủ cục bộ của bạn và Rack khởi tạo MessageApp
và chạy call
, chuyển trong Môi trường Hash dưới dạng tham số vào phương thức ( các env
tham số).
Rack lấy giá trị trả về (mảng) và sử dụng nó để tạo một thể hiện Rack::Response
và gửi nó trở lại Máy khách. Trình duyệt sử dụng phép thuật để in 'Xin chào, Thế giới!' đến màn hình.
Ngẫu nhiên, nếu bạn muốn xem băm môi trường trông như thế nào, chỉ cần đặt puts env
bên dướidef call(env)
.
Tối thiểu như nó là, những gì bạn đã viết ở đây là một ứng dụng Rack!
Trong ứng dụng Rack nhỏ của chúng tôi, chúng tôi có thể tương tác với env
hàm băm (xem tại đây để biết thêm về băm Môi trường).
Chúng tôi sẽ triển khai khả năng người dùng nhập chuỗi truy vấn của riêng họ vào URL, do đó, chuỗi đó sẽ có mặt trong yêu cầu HTTP, được gói gọn dưới dạng một giá trị trong một trong các cặp khóa / giá trị của hàm băm Môi trường.
Ứng dụng Rack của chúng tôi sẽ truy cập chuỗi truy vấn đó từ hàm băm Môi trường và gửi lại cho khách hàng (trình duyệt của chúng tôi, trong trường hợp này) thông qua Phần thân trong Phản hồi.
Từ các tài liệu Rack trên Hash môi trường: "QUERY_STRING: Phần của URL yêu cầu theo sau ?, Nếu có. Có thể trống, nhưng luôn luôn được yêu cầu!"
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
Bây giờ, rackup
và truy cập localhost:9292?hello
( ?hello
là chuỗi truy vấn) và bạn sẽ thấy 'xin chào' được hiển thị trong chế độ xem.
Chúng tôi sẽ:
MessageSetter
,env
,MessageSetter
sẽ chèn một 'MESSAGE'
khóa vào hàm băm env, giá trị của nó là 'Hello, World!'
nếu env['QUERY_STRING']
trống; env['QUERY_STRING']
nếu không,@app.call(env)
- @app
là ứng dụng tiếp theo trong 'Stack' : MessageApp
.Đầu tiên, phiên bản 'tay dài':
#./middleware/message_setter.rb
class MessageSetter
def initialize(app)
@app = app
end
def call(env)
if env['QUERY_STRING'].empty?
env['MESSAGE'] = 'Hello, World!'
else
env['MESSAGE'] = env['QUERY_STRING']
end
@app.call(env)
end
end
#./message_app.rb (same as before)
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
#config.ru
require_relative './message_app'
require_relative './middleware/message_setter'
app = Rack::Builder.new do
use MessageSetter
run MessageApp.new
end
run app
Từ các tài liệu Rack :: Builder chúng ta thấy rằngRack::Builder
thực hiện một DSL nhỏ để lặp lại các ứng dụng Rack. Về cơ bản, điều này có nghĩa là bạn có thể xây dựng một 'Ngăn xếp' bao gồm một hoặc nhiều Middleware và ứng dụng 'cấp dưới' để gửi tới. Tất cả các yêu cầu chuyển đến ứng dụng cấp dưới của bạn sẽ được xử lý trước bởi (các) Middleware của bạn.
#use
chỉ định phần mềm trung gian để sử dụng trong ngăn xếp. Nó lấy phần mềm trung gian làm đối số.
Rack Middleware phải:
call
phương thức lấy hàm băm Môi trường làm tham số.Trong trường hợp của chúng tôi, 'Middleware' là MessageSetter
, 'constructor' là initialize
phương thức của MessageSetter , 'ứng dụng tiếp theo' trong ngăn xếp làMessageApp
.
Vì vậy, ở đây, vì những gì Rack::Builder
không dưới mui xe, các app
đối số của MessageSetter
's initialize
phương pháp là MessageApp
.
(hãy tập trung vào những điều trên trước khi tiếp tục)
Do đó, mỗi phần của Middleware về cơ bản 'truyền' băm Môi trường hiện tại cho ứng dụng tiếp theo trong chuỗi - vì vậy bạn có cơ hội để biến đổi băm môi trường đó trong Middleware trước khi chuyển nó sang ứng dụng tiếp theo trong ngăn xếp.
#run
nhận một đối số là một đối tượng phản hồi #call
và trả về một Phản hồi giá (một thể hiện của Rack::Response
).
Sử dụng Rack::Builder
bạn có thể xây dựng chuỗi Middleware và mọi yêu cầu cho ứng dụng của bạn sẽ được xử lý lần lượt bởi mỗi Middleware trước khi cuối cùng được xử lý bởi phần cuối cùng trong ngăn xếp (trong trường hợp của chúng tôi,MessageApp
). Điều này cực kỳ hữu ích vì nó tách ra các giai đoạn xử lý yêu cầu khác nhau. Về mặt 'phân tách mối quan tâm', nó không thể sạch sẽ hơn nhiều!
Bạn có thể xây dựng một 'đường ống yêu cầu' bao gồm một số Middleware xử lý những thứ như:
(trên các gạch đầu dòng từ một câu trả lời khác về chủ đề này)
Bạn sẽ thường thấy điều này trong các ứng dụng Sinatra chuyên nghiệp. Sinatra sử dụng Rack! Xem ở đây để định nghĩa về Sinatra IS !
Như một lưu ý cuối cùng, chúng ta config.ru
có thể được viết theo kiểu tay ngắn, tạo ra chính xác cùng chức năng (và đây là những gì bạn thường thấy):
require_relative './message_app'
require_relative './middleware/message_setter'
use MessageSetter
run MessageApp.new
Và để thể hiện rõ hơn những gì MessageApp
đang làm, đây là phiên bản 'tay dài' của nó cho thấy rõ ràng rằng nó #call
đang tạo ra một thể hiện mới Rack::Response
, với ba đối số được yêu cầu.
class MessageApp
def call(env)
Rack::Response.new([env['MESSAGE']], 200, {})
end
end
Rack - Giao diện b / w Web & App Server
Rack là gói Ruby cung cấp giao diện cho máy chủ web để giao tiếp với ứng dụng. Thật dễ dàng để thêm các thành phần phần mềm trung gian giữa máy chủ web và ứng dụng để sửa đổi cách ứng xử / yêu cầu của bạn. Thành phần phần mềm trung gian nằm giữa máy khách và máy chủ, xử lý các yêu cầu gửi đến và phản hồi ra ngoài.
Nói một cách dễ hiểu, về cơ bản, đây chỉ là một bộ hướng dẫn về cách một máy chủ và ứng dụng Rails (hoặc bất kỳ ứng dụng web Ruby nào khác) nên nói chuyện với nhau .
Để sử dụng Rack, hãy cung cấp "ứng dụng": một đối tượng phản hồi lại phương thức gọi, lấy hàm băm môi trường làm tham số và trả về một mảng có ba phần tử:
Để giải thích thêm, bạn có thể theo các liên kết dưới đây.
1. https://rack.github.io/
2. https://redpanthers.co/rack-middleware/
3. https://blog.engineyard.com/2015/understanding-rack-apps-and-middleware
4. https://guides.rubyonrails.org/rails_on_rack.html#resources
Trong rails, chúng tôi có config.ru dưới dạng tệp rack, bạn có thể chạy bất kỳ tệp rack nào bằng rackup
lệnh. Và cổng mặc định cho việc này là 9292
. Để kiểm tra điều này, bạn chỉ cần chạy rackup
trong thư mục rails của bạn và xem kết quả. Bạn cũng có thể gán cổng mà bạn muốn chạy nó. Lệnh chạy tệp rack trên bất kỳ cổng cụ thể nào là
rackup -p PORT_NUMBER
Rack là một viên ngọc cung cấp giao diện đơn giản cho yêu cầu / phản hồi HTTP trừu tượng. Rack nằm giữa các khung web (Rails, Sinatra, v.v.) và các máy chủ web (kỳ lân, puma) như một bộ chuyển đổi. Từ hình ảnh trên, điều này giữ cho máy chủ kỳ lân hoàn toàn độc lập với việc biết về đường ray và đường ray không biết về kỳ lân. Đây là một ví dụ tốt về khớp nối lỏng lẻo , tách các mối quan tâm .
Hình trên là từ cuộc hội thảo rails này trên rack https://youtu.be/3PnUV9QzB0g Tôi khuyên bạn nên xem nó để hiểu sâu hơn.