Computer >> Máy Tính >  >> Lập trình >> Ruby

Phân tích nhanh về cách thức hoạt động của Sinatra

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 :

  1. 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”
  2. Bộ lọc trước khi chạy
  3. Đối sánh tuyến đường
  4. 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ó 🙂