sinatra
1ヶ月くらい前に7年ぶりのメジャーアップデートリリースがあった.rack2.0, ruby 2.2+ に対応したり rails5 との互換性が実装されたらしい.
コードリーディングは, sinatra の classic app の
rackup
コマンドなしでサーバーが起動する部分- DSLの実装
を読んでいく.
Rack
Rackに関してここでは説明しないが前提知識としてあったほうがいい. k0kubunさんの記事がとてもわかりやすかったです.
http://qiita.com/k0kubun/items/248395f68164b52aec4a
server の起動
entry point
require './app' run Sinatra::Application
これだけで app.rb
でDSLを書けば ruby app.rb
コマンドでサーバが起動する.
Sinatra::Application
をみると,at_exit
で Application.run!
をフックしてる
at_exit { Application.run! if $!.nil? && Application.run? }
Sinatra::Base#run!
Sinatra::Application
は Sinatra::Base
のサブクラス.
#detect_rack_handler
で
ひっぱってきたRackハンドラに対して #start_server
で handler.run
してサーバを起動してる
def run!(options = {}, &block) return if running? set options handler = detect_rack_handler handler_name = handler.name.gsub(/.*::/, '') server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {} server_settings.merge!(:Port => port, :Host => bind) begin start_server(handler, server_settings, handler_name, &block) rescue Errno::EADDRINUSE $stderr.puts "== Someone is already performing on port #{port}!" raise ensure quit! end end
DSLの実装
Rack::Builder
で Sinatra::Delegator
を include してる
class Rack::Builder include Sinatra::Delegator end
Sinatra::Delegator
はDSLのメソッドを define_method
で定義して,Sinatra::Application
に send
してる.
module Delegator #:nodoc: def self.delegate(*methods) methods.each do |method_name| define_method(method_name) do |*args, &block| return super(*args, &block) if respond_to? method_name Delegator.target.send(method_name, *args, &block) end private method_name end end delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink, :template, :layout, :before, :after, :error, :not_found, :configure, :set, :mime_type, :enable, :disable, :use, :development?, :test?, :production?, :helpers, :settings, :register class << self attr_accessor :target end self.target = Application end
Rack application
main.rb
で フックしてるのは Application.run
なので,Sinatra::Application#call
ではなく Sinatra::Application.call
が呼ばれる(実装は両方ある)
def call(env) synchronize { prototype.call(env) } end
prototypeにはrack middlewareでラップされたrack application が入ってるっぽいので結局 #call
がよばれるみたいだけどちょっと自信ない.
def call!(env) # :nodoc: @env = env @request = Request.new(env) @response = Response.new template_cache.clear if settings.reload_templates @response['Content-Type'] = nil invoke { dispatch! } invoke { error_block!(response.status) } unless @env['sinatra.error'] unless @response['Content-Type'] if Array === body and body[0].respond_to? :content_type content_type body[0].content_type else content_type :html end end @response.finish end
dispatch!
で #route!
がよばれてその中でroutingしてるっぽいけどこの辺はあんまりちゃんとよんでない.
かんそう
rackに関してちょっと勉強になった. at_exit
でフックしてるのはだいぶアクロバティックに見えるけどどうなんだろ.
とりあえず1984行の base.rb
は迫力満点なのでファイル分割してほしい