Sinatra là một khuôn khổ web Ruby.
Nó giống như em trai Rails…
Hãy khám phá cách Sinatra hoạt động :
- Điều gì xảy ra khi bạn yêu cầu Sinatra vào dự án của mình?
- Đối sánh tuyến đường hoạt động như thế nào?
- Các yêu cầu và phản hồi được xử lý như thế nào?
Rất nhiều câu hỏi, nhưng quá ít thời gian…
Không sao!
Tôi đã làm việc chăm chỉ cho bạn và tổng hợp bài viết này để tôi trả lời những câu hỏi này để bạn có thể học nhanh hơn!
Khởi tạo Sinatra
Tất cả bắt đầu với một tệp:sinatra.rb
.
Tất cả những gì tệp này làm là yêu cầu main.rb
, không thú vị lắm phải không?
Giờ đây, nó trở nên thú vị hơn!
Bên trong main.rb
bạn sẽ tìm thấy yêu cầu cho base.rb
&bạn cũng sẽ tìm thấy mã để phân tích cú pháp tùy chọn (cổng, môi trường, chế độ yên tĩnh, v.v.).
Sinatra sử dụng optparse
, từ thư viện tiêu chuẩn của Ruby.
Bạn có thể tìm thấy gì khác ở đây?
Hãy xem at_exit
này khối:
at_exit { Application.run! if $!.nil? && Application.run? }
Đây là một đoạn mã sẽ chạy khi chương trình kết thúc.
Điều xảy ra là tất cả mã của bạn sẽ được đọc bởi Ruby và vì bạn không có bất kỳ vòng lặp, trạng thái ngủ hoặc bất kỳ điều gì tương tự như vậy chương trình của bạn sẽ kết thúc một cách tự nhiên.
… Nhưng ngay trước khi nó kết thúc at_exit
khối sẽ kích hoạt!
Sau đó, Sinatra tiếp quản và khởi động một máy chủ web để nó có thể xử lý các yêu cầu.
Đây là mã thực hiện điều đó :
begin start_server(handler, server_settings, handler_name, &block) rescue Errno::EADDRINUSE $stderr.puts "== Someone is already performing on port #{port}!" raise end # Part of base.rb `run!` method
Ồ và một điều quan trọng khác xảy ra ở đây:
extend Sinatra::Delegator
Sinatra::Delegator
là một mô-đun xác định các phương thức Sinatra DSL như get
, post
&set
.
Đó là lý do tại sao bạn có thể làm điều này:
get '/' do puts "Hello World!" end
Sinatra mở rộng main
toàn cầu đối tượng với mô-đun này.
Xử lý yêu cầu &phản hồi
Được rồi, tại thời điểm này, chúng tôi có một máy chủ đang chạy sẵn sàng chấp nhận các kết nối mới.
Nhưng điều gì sẽ xảy ra khi nhận được kết nối mới?
Cũng giống như Sinatra, giống như Rails và các khuôn khổ web Ruby khác, sử dụng Rack để xử lý tất cả các nội dung cấp thấp hơn.
Rack mong đợi một cuộc gọi call
phương pháp có sẵn trên ứng dụng của bạn. Đó chỉ là một đối tượng mà bạn cung cấp cho Rack khi khởi tạo nó.
Trong trường hợp của Sinatra, đối tượng này là Sinatra::Base
lớp học.
Đây là phương pháp :
# Rack call interface. def call!(env) @env = env @request = Request.new(env) @response = Response.new invoke { dispatch! } invoke { error_block!(response.status) } unless @env['sinatra.error'] @response.finish end # Modified version of Sinatra's call method (for clarity)
Có vẻ như chúng tôi cần điều tra dispatch!
bên cạnh để hiển thị cách một yêu cầu được xử lý.
Đây là phương pháp :
def dispatch! invoke do static! if settings.static? && (request.get? || request.head?) filter! :before route! end rescue ::Exception => boom invoke { handle_exception!(boom) } ensure filter! :after unless env['sinatra.static_file'] end # Edited down to the important parts
Yêu cầu được chia thành 4 bước :
- Các tệp tĩnh được chọn trước tiên. Đây là các tệp như css, js &hình ảnh. Cài đặt này được bật theo mặc định nếu tồn tại một thư mục có tên “công khai”
- Bộ lọc trước khi chạy
- Đối sánh tuyến đường
- Bộ lọc sau khi chạy
Bây giờ chúng ta có thể đi sâu vào từng bước để xem điều gì xảy ra chi tiết hơn.
Cung cấp tệp tĩnh
static!
phương pháp khá đơn giản:
def static!(options = {}) return if (public_dir = settings.public_folder).nil? path = File.expand_path("#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}" ) return unless File.file?(path) cache_control(*settings.static_cache_control) if settings.static_cache_control? send_file(path, options) end
Mã này kiểm tra xem tệp được yêu cầu có tồn tại hay không, sau đó nó đặt tiêu đề HTTP “Kiểm soát bộ nhớ cache”.
Ở dòng cuối cùng, nó gọi send_file
&nó đúng như tên gọi 🙂
Trước khi lọc
Bộ lọc before cho phép bạn chạy mã trước khi cố gắng tìm một tuyến đường phù hợp.
Đây là cách bộ lọc được thêm vào:
# Define a before filter. # Runs before all requests within the same context as route handlers # and may access/modify the request and response. @filters = {:before => [], :after => []} def before(path = /.*/, **options, &block) add_filter(:before, path, options, &block) end def after(path = /.*/, **options, &block) add_filter(:after, path, options, &block) end def add_filter(type, path = /.*/, **options, &block) filters[type] << compile!(type, path, block, options) end
Như bạn có thể thấy filters
chỉ là một hàm băm với hai khóa, một khóa cho mỗi loại bộ lọc.
Nhưng compile!
?
Phương thức này trả về một mảng có 3 phần tử:một mẫu, một mảng điều kiện và một trình bao bọc.
Phương pháp tương tự cũng được sử dụng để tạo các tuyến (khi bạn sử dụng get
hoặc post
khối):
def get(path, opts = {}, &block) route('GET', path, opts, &block) end def route(verb, path, options = {}, &block) signature = compile!(verb, path, block, options) (@routes[verb] ||= []) << signature signature end # Methods edited for clarity
Từ điều này, chúng ta có thể biết rằng bộ lọc Sinatra hoạt động và hoạt động giống như các tuyến đường.
Đối sánh tuyến đường
Bước tiếp theo trong chu trình xử lý yêu cầu là đối sánh tuyến đường:
def route!(base = settings, pass_block = nil) routes = base.routes[@request.request_method] routes.each do |pattern, conditions, block| process_route(pattern, conditions) route_eval end route_missing end # Edited method
Mã này đi qua mọi tuyến đường phù hợp với phương thức yêu cầu (get
, post
, v.v.).
Đối sánh tuyến đường xảy ra bên trong process_route
phương pháp:
def process_route(pattern, keys, conditions, block = nil, values = []) route = @request.path_info route = '/' if route.empty? and not settings.empty_path_info? return unless match = pattern.match(route) end
Ở đâu pattern
là một biểu thức chính quy.
Nếu một tuyến đường phù hợp với cả đường dẫn và điều kiện thì route_eval
sẽ được gọi, đánh giá khối (phần thân của get
của bạn / post
tuyến đường) &kết thúc quá trình đối sánh tuyến đường.
# Run a route block and throw :halt with the result. def route_eval throw :halt, yield end
Điều này sử dụng catch
bất thường / throw
cơ chế kiểm soát dòng chảy.
Tôi khuyên bạn không nên sử dụng nó vì có thể rất khó hiểu khi làm theo dòng mã, nhưng thật thú vị khi xem một ví dụ thực tế về tính năng này đang được sử dụng.
Xây dựng phản hồi
Bước cuối cùng của chu trình yêu cầu là chuẩn bị phản hồi.
Vậy phản hồi sẽ đi đến đâu?
invoke
phương thức thu thập phản hồi như sau:
res = catch(:halt) { yield }
Kết quả này được gán cho phần thân phản hồi bằng body
phương pháp:
body(res)
Bây giờ nếu chúng ta nhìn lại nơi chúng ta đã bắt đầu, call
, chúng tôi sẽ tìm thấy dòng mã này:
@response.finish
Điều này gọi finish
phương thức trên @response
, là Rack::Response
đối tượng.
Nói cách khác, điều này thực sự sẽ kích hoạt phản hồi được gửi đến máy khách.
Phần thưởng:Phương pháp đặt hoạt động như thế nào
Phương thức thiết lập là một phần của DSL (Ngôn ngữ dành riêng cho miền) của Sinatra và nó cho phép bạn đặt các tùy chọn cấu hình ở bất kỳ đâu trong ứng dụng Sinatra của bạn.
Ví dụ :
set :public_folder, '/var/www'
Mỗi khi bạn sử dụng set
Sinatra tạo ra 3 phương thức (thông qua lập trình siêu ứng dụng):
define_singleton("#{option}=", setter) if setter define_singleton(option, getter) if getter define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
3 phương pháp là (với public_folder
ví dụ):
- public_folder
- public_folder =
- public_folder?
Phương thức này cũng sẽ gọi phương thức setter (public_folder=
) nếu nó đã tồn tại:
if respond_to?("#{option}=") && !ignore_setter return __send__("#{option}=", value) end
Hãy nhớ rằng lập trình siêu hình không miễn phí, vì vậy tôi sẽ chỉ gắn bó với một options
băm. Bạn không cần những phương pháp ưa thích đó.
Tóm tắt
Bạn đã biết cách Sinatra được khởi tạo, cách nó xử lý một yêu cầu và các bước khác nhau mà nó thực hiện cho đến khi có phản hồi. Điều này sẽ giúp bạn học một vài thủ thuật Ruby và hiểu Sinatra hơn!
Đừng quên chia sẻ bài đăng này với các nhà phát triển Ruby khác để họ cũng có thể học hỏi từ nó 🙂