From 704f843054f3ca32941d42972a1c7d1b144d06ad Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 9 May 2011 04:39:54 +0000 Subject: configurator: move validation logic over There's actually no reason we can't have these methods in Rainbows::Configurator where it's easier to document nowadays. --- lib/rainbows/client.rb | 7 +-- lib/rainbows/configurator.rb | 79 +++++++++++++++++++++++++++++- lib/rainbows/coolio.rb | 2 +- lib/rainbows/coolio/client.rb | 4 +- lib/rainbows/coolio/heartbeat.rb | 3 +- lib/rainbows/coolio_fiber_spawn.rb | 1 + lib/rainbows/epoll/client.rb | 4 +- lib/rainbows/ev_core.rb | 4 +- lib/rainbows/event_machine/client.rb | 3 +- lib/rainbows/fiber/base.rb | 1 + lib/rainbows/fiber/coolio/methods.rb | 1 - lib/rainbows/fiber/io.rb | 1 - lib/rainbows/http_server.rb | 76 ++++++---------------------- lib/rainbows/max_body.rb | 11 +++-- lib/rainbows/process_client.rb | 4 +- lib/rainbows/response.rb | 2 +- lib/rainbows/revactor/client.rb | 4 +- lib/rainbows/xepoll_thread_pool/client.rb | 6 +-- lib/rainbows/xepoll_thread_spawn/client.rb | 8 +-- 19 files changed, 131 insertions(+), 90 deletions(-) (limited to 'lib/rainbows') diff --git a/lib/rainbows/client.rb b/lib/rainbows/client.rb index 4608f53..b044f26 100644 --- a/lib/rainbows/client.rb +++ b/lib/rainbows/client.rb @@ -5,20 +5,21 @@ require "io/wait" # this class is used for most synchronous concurrency models class Rainbows::Client < Kgio::Socket include Rainbows::ProcessClient + Rainbows.config!(self, :keepalive_timeout) def read_expire - Time.now + Rainbows.keepalive_timeout + Time.now + KEEPALIVE_TIMEOUT end def kgio_wait_readable - wait Rainbows.keepalive_timeout + wait KEEPALIVE_TIMEOUT end # used for reading headers (respecting keepalive_timeout) def timed_read(buf) expire = nil begin - case rv = kgio_tryread(HBUFSIZ, buf) + case rv = kgio_tryread(CLIENT_HEADER_BUFFER_SIZE, buf) when :wait_readable return if expire && expire < Time.now expire ||= read_expire diff --git a/lib/rainbows/configurator.rb b/lib/rainbows/configurator.rb index b596b0b..95bb590 100644 --- a/lib/rainbows/configurator.rb +++ b/lib/rainbows/configurator.rb @@ -3,6 +3,14 @@ # This module adds \Rainbows! to the # {Unicorn::Configurator}[http://unicorn.bogomips.org/Unicorn/Configurator.html] module Rainbows::Configurator + Unicorn::Configurator::DEFAULTS.merge!({ + :use => Rainbows::Base, + :worker_connections => 50, + :keepalive_timeout => 5, + :keepalive_requests => 100, + :client_max_body_size => 1024 * 1024, + :client_header_buffer_size => 1024, + }) # configures \Rainbows! with a given concurrency model to +use+ and # a +worker_connections+ upper-bound. This method may be called @@ -45,7 +53,76 @@ module Rainbows::Configurator # denial-of-service attacks that use HTTP pipelining. def Rainbows!(&block) block_given? or raise ArgumentError, "Rainbows! requires a block" - Rainbows::HttpServer.setup(block) + @block = true + instance_eval(&block) + ensure + @block = false + end + + def check! # :nodoc: + @block or abort "must be inside a Rainbows! block" + end + + def worker_connections(nr) + check! + set_int(:worker_connections, nr, 1) + end + + def use(model, *options) + check! + mod = begin + Rainbows.const_get(model) + rescue NameError => e + warn "error loading #{model.inspect}: #{e}" + e.backtrace.each { |l| warn l } + abort "concurrency model #{model.inspect} not supported" + end + Module === mod or abort "concurrency model #{model.inspect} not supported" + options.each do |opt| + case opt + when Hash + Rainbows::O.merge!(opt) + when Symbol + Rainbows::O[opt] = true + else + abort "cannot handle option: #{opt.inspect} in #{options.inspect}" + end + end + mod.setup if mod.respond_to?(:setup) + set[:use] = mod + end + + def keepalive_timeout(seconds) + check! + set_int(:keepalive_timeout, seconds, 0) + end + + def keepalive_requests(count) + check! + case count + when nil, Integer + set[:keepalive_requests] = count + else + abort "not an integer or nil: keepalive_requests=#{count.inspect}" + end + end + + def client_max_body_size(bytes) + check! + err = "client_max_body_size must be nil or a non-negative Integer" + case bytes + when nil + when Integer + bytes >= 0 or abort err + else + abort err + end + set[:client_max_body_size] = bytes + end + + def client_header_buffer_size(bytes) + check! + set_int(:client_header_buffer_size, bytes, 1) end end diff --git a/lib/rainbows/coolio.rb b/lib/rainbows/coolio.rb index 59bfde6..b62e02b 100644 --- a/lib/rainbows/coolio.rb +++ b/lib/rainbows/coolio.rb @@ -39,10 +39,10 @@ module Rainbows::Coolio autoload :ThreadClient, 'rainbows/coolio/thread_client' autoload :ResponsePipe, 'rainbows/coolio/response_pipe' autoload :ResponseChunkPipe, 'rainbows/coolio/response_chunk_pipe' + autoload :Heartbeat, 'rainbows/coolio/heartbeat' # :startdoc: end # :enddoc: -require 'rainbows/coolio/heartbeat' require 'rainbows/coolio/server' require 'rainbows/coolio/core' Rainbows::Coolio.__send__ :include, Rainbows::Coolio::Core diff --git a/lib/rainbows/coolio/client.rb b/lib/rainbows/coolio/client.rb index 5688730..5d2edec 100644 --- a/lib/rainbows/coolio/client.rb +++ b/lib/rainbows/coolio/client.rb @@ -45,7 +45,7 @@ class Rainbows::Coolio::Client < Coolio::IO end def on_readable - buf = @_io.kgio_tryread(HBUFSIZ, RBUF) + buf = @_io.kgio_tryread(CLIENT_HEADER_BUFFER_SIZE, RBUF) case buf when :wait_readable when nil # eof @@ -134,7 +134,7 @@ class Rainbows::Coolio::Client < Coolio::IO close if @_write_buffer.empty? when :headers if @buf.empty? - buf = @_io.kgio_tryread(HBUFSIZ, RBUF) or return close + buf = @_io.kgio_tryread(CLIENT_HEADER_BUFFER_SIZE, RBUF) or return close String === buf and return on_read(buf) # buf == :wait_readable unless enabled? diff --git a/lib/rainbows/coolio/heartbeat.rb b/lib/rainbows/coolio/heartbeat.rb index 4657155..f58ed33 100644 --- a/lib/rainbows/coolio/heartbeat.rb +++ b/lib/rainbows/coolio/heartbeat.rb @@ -8,9 +8,10 @@ class Rainbows::Coolio::Heartbeat < Coolio::TimerWatcher KATO = Rainbows::Coolio::KATO CONN = Rainbows::Coolio::CONN + Rainbows.config!(self, :keepalive_timeout) def on_timer - if (ot = Rainbows.keepalive_timeout) >= 0 + if (ot = KEEPALIVE_TIMEOUT) >= 0 ot = Time.now - ot KATO.delete_if { |client, time| time < ot and client.timeout? } end diff --git a/lib/rainbows/coolio_fiber_spawn.rb b/lib/rainbows/coolio_fiber_spawn.rb index 7b00d58..88583f8 100644 --- a/lib/rainbows/coolio_fiber_spawn.rb +++ b/lib/rainbows/coolio_fiber_spawn.rb @@ -25,6 +25,7 @@ module Rainbows::CoolioFiberSpawn Server.const_set(:APP, Rainbows.server.app) Heartbeat.new(1, true).attach(Coolio::Loop.default) LISTENERS.map! { |s| Server.new(s).attach(Coolio::Loop.default) } + Rainbows::Client.__send__ :include, Rainbows::Fiber::Coolio::Methods Coolio::Loop.default.run end end diff --git a/lib/rainbows/epoll/client.rb b/lib/rainbows/epoll/client.rb index 520eb2b..0d6a8c0 100644 --- a/lib/rainbows/epoll/client.rb +++ b/lib/rainbows/epoll/client.rb @@ -10,7 +10,7 @@ module Rainbows::Epoll::Client OUT = SleepyPenguin::Epoll::OUT | SleepyPenguin::Epoll::ET KATO = {} KATO.compare_by_identity if KATO.respond_to?(:compare_by_identity) - KEEPALIVE_TIMEOUT = Rainbows.keepalive_timeout + Rainbows.config!(self, :keepalive_timeout) EP = Rainbows::Epoll::EP @@last_expire = Time.now @@ -33,7 +33,7 @@ module Rainbows::Epoll::Client end def on_readable - case rv = kgio_tryread(HBUFSIZ, RBUF) + case rv = kgio_tryread(CLIENT_HEADER_BUFFER_SIZE, RBUF) when String on_read(rv) return if @wr_queue[0] || closed? diff --git a/lib/rainbows/ev_core.rb b/lib/rainbows/ev_core.rb index fb19b04..030c8e1 100644 --- a/lib/rainbows/ev_core.rb +++ b/lib/rainbows/ev_core.rb @@ -9,7 +9,7 @@ module Rainbows::EvCore autoload :CapInput, 'rainbows/ev_core/cap_input' RBUF = "" Z = "".freeze - HBUFSIZ = Rainbows.client_header_buffer_size + Rainbows.config!(self, :client_header_buffer_size) # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ] ASYNC_CALLBACK = "async.callback".freeze @@ -133,7 +133,7 @@ module Rainbows::EvCore end def mkinput - max = Rainbows.client_max_body_size + max = Rainbows.server.client_max_body_size len = @hp.content_length if len if max && (len > max) diff --git a/lib/rainbows/event_machine/client.rb b/lib/rainbows/event_machine/client.rb index 8c2549d..e56931f 100644 --- a/lib/rainbows/event_machine/client.rb +++ b/lib/rainbows/event_machine/client.rb @@ -2,6 +2,7 @@ # :enddoc: class Rainbows::EventMachine::Client < EM::Connection include Rainbows::EvCore + Rainbows.config!(self, :keepalive_timeout) def initialize(io) @_io = io @@ -87,7 +88,7 @@ class Rainbows::EventMachine::Client < EM::Connection if alive if @deferred.nil? if @buf.empty? - set_comm_inactivity_timeout(Rainbows.keepalive_timeout) + set_comm_inactivity_timeout(KEEPALIVE_TIMEOUT) else EM.next_tick { receive_data(nil) } end diff --git a/lib/rainbows/fiber/base.rb b/lib/rainbows/fiber/base.rb index a4f2341..00af214 100644 --- a/lib/rainbows/fiber/base.rb +++ b/lib/rainbows/fiber/base.rb @@ -64,6 +64,7 @@ module Rainbows::Fiber::Base end def self.setup(klass, app) + Rainbows::Client.__send__(:include, Rainbows::Fiber::IO::Methods) require 'rainbows/fiber/body' Rainbows::Client.__send__(:include, Rainbows::Fiber::Body) self.const_set(:APP, app) diff --git a/lib/rainbows/fiber/coolio/methods.rb b/lib/rainbows/fiber/coolio/methods.rb index 64b0ee6..2e664ff 100644 --- a/lib/rainbows/fiber/coolio/methods.rb +++ b/lib/rainbows/fiber/coolio/methods.rb @@ -38,7 +38,6 @@ end [ Rainbows::Fiber::IO, - Rainbows::Client, # the next two trigger autoload, ugh, oh well... Rainbows::Fiber::IO::Socket, Rainbows::Fiber::IO::Pipe diff --git a/lib/rainbows/fiber/io.rb b/lib/rainbows/fiber/io.rb index 3005d96..0eed4ad 100644 --- a/lib/rainbows/fiber/io.rb +++ b/lib/rainbows/fiber/io.rb @@ -101,7 +101,6 @@ end # :stopdoc: require 'rainbows/fiber/io/methods' require 'rainbows/fiber/io/compat' -Rainbows::Client.__send__(:include, Rainbows::Fiber::IO::Methods) class Rainbows::Fiber::IO include Rainbows::Fiber::IO::Compat include Rainbows::Fiber::IO::Methods diff --git a/lib/rainbows/http_server.rb b/lib/rainbows/http_server.rb index 8732e68..62a5927 100644 --- a/lib/rainbows/http_server.rb +++ b/lib/rainbows/http_server.rb @@ -2,6 +2,12 @@ # :enddoc: class Rainbows::HttpServer < Unicorn::HttpServer + attr_accessor :worker_connections + attr_accessor :keepalive_timeout + attr_accessor :client_header_buffer_size + attr_accessor :client_max_body_size + attr_reader :use + def self.setup(block) Rainbows.server.instance_eval(&block) end @@ -10,7 +16,7 @@ class Rainbows::HttpServer < Unicorn::HttpServer Rainbows.server = self @logger = Unicorn::Configurator::DEFAULTS[:logger] super(app, options) - defined?(@use) or use(:Base) + defined?(@use) or self.use = Rainbows::Base @worker_connections ||= @use == :Base ? 1 : 50 end @@ -33,14 +39,12 @@ class Rainbows::HttpServer < Unicorn::HttpServer end def load_config! - use :Base - Rainbows.defaults! - @worker_connections = nil super - @worker_connections ||= @use == :Base ? 1 : 50 + @worker_connections = 1 if @use == :Base end def worker_loop(worker) + Rainbows.forked = true orig = method(:worker_loop) extend(Rainbows.const_get(@use)) m = method(:worker_loop) @@ -80,70 +84,22 @@ class Rainbows::HttpServer < Unicorn::HttpServer File.basename($0) end - def use(*args) - model = args.shift or return @use - mod = begin - Rainbows.const_get(model) - rescue NameError => e - logger.error "error loading #{model.inspect}: #{e}" - e.backtrace.each { |l| logger.error l } - raise ArgumentError, "concurrency model #{model.inspect} not supported" - end - - Module === mod or - raise ArgumentError, "concurrency model #{model.inspect} not supported" - args.each do |opt| - case opt - when Hash; Rainbows::O.update(opt) - when Symbol; Rainbows::O[opt] = true - else; raise ArgumentError, "can't handle option: #{opt.inspect}" - end - end - mod.setup if mod.respond_to?(:setup) + def use=(mod) + @use = mod.to_s.split(/::/)[-1].to_sym new_defaults = { - 'rainbows.model' => (@use = model.to_sym), - 'rack.multithread' => !!(model.to_s =~ /Thread/), + 'rainbows.model' => @use, + 'rack.multithread' => !!(mod.to_s =~ /Thread/), 'rainbows.autochunk' => [:Coolio,:Rev,:Epoll,:XEpoll, :EventMachine,:NeverBlock].include?(@use), } Rainbows::Const::RACK_DEFAULTS.update(new_defaults) end - def worker_connections(*args) - return @worker_connections if args.empty? - nr = args[0] - (Integer === nr && nr > 0) or - raise ArgumentError, "worker_connections must be a positive Integer" - @worker_connections = nr - end - - def keepalive_timeout(nr) - (Integer === nr && nr >= 0) or - raise ArgumentError, "keepalive_timeout must be a non-negative Integer" - Rainbows.keepalive_timeout = nr - end - - def keepalive_requests(nr) - Integer === nr or - raise ArgumentError, "keepalive_requests must be a non-negative Integer" + def keepalive_requests=(nr) Unicorn::HttpRequest.keepalive_requests = nr end - def client_max_body_size(nr) - err = "client_max_body_size must be nil or a non-negative Integer" - case nr - when nil - when Integer - nr >= 0 or raise ArgumentError, err - else - raise ArgumentError, err - end - Rainbows.client_max_body_size = nr - end - - def client_header_buffer_size(bytes) - Integer === bytes && bytes > 0 or raise ArgumentError, - "client_header_buffer_size must be a positive Integer" - Rainbows.client_header_buffer_size = bytes + def keepalive_requests + Unicorn::HttpRequest.keepalive_requests end end diff --git a/lib/rainbows/max_body.rb b/lib/rainbows/max_body.rb index 33ba572..aedc9e9 100644 --- a/lib/rainbows/max_body.rb +++ b/lib/rainbows/max_body.rb @@ -21,8 +21,12 @@ class Rainbows::MaxBody # This is automatically called when used with Rack::Builder#use - def initialize(app, limit = Rainbows.client_max_body_size) - Integer === limit or raise ArgumentError, "limit not an Integer" + def initialize(app, limit = nil) + case limit + when Integer, nil + else + raise ArgumentError, "limit not an Integer" + end @app, @limit = app, limit end @@ -33,6 +37,7 @@ class Rainbows::MaxBody # our main Rack middleware endpoint def call(env) + @limit = Rainbows.server.client_max_body_size if nil == @limit catch(:rainbows_EFBIG) do len = env[CONTENT_LENGTH] if len && len.to_i > @limit @@ -47,7 +52,7 @@ class Rainbows::MaxBody # this is called after forking, so it won't ever affect the master # if it's reconfigured def self.setup # :nodoc: - Rainbows.client_max_body_size or return + Rainbows.server.client_max_body_size or return case Rainbows.server.use when :Rev, :Coolio, :EventMachine, :NeverBlock, :RevThreadSpawn, :RevThreadPool, diff --git a/lib/rainbows/process_client.rb b/lib/rainbows/process_client.rb index 3f23825..53b4f53 100644 --- a/lib/rainbows/process_client.rb +++ b/lib/rainbows/process_client.rb @@ -7,11 +7,11 @@ module Rainbows::ProcessClient NULL_IO = Unicorn::HttpRequest::NULL_IO RACK_INPUT = Unicorn::HttpRequest::RACK_INPUT IC = Unicorn::HttpRequest.input_class - HBUFSIZ = Rainbows.client_header_buffer_size + Rainbows.config!(self, :client_header_buffer_size) def process_loop @hp = hp = Rainbows::HttpParser.new - kgio_read!(HBUFSIZ, buf = hp.buf) or return + kgio_read!(CLIENT_HEADER_BUFFER_SIZE, buf = hp.buf) or return begin # loop until env = hp.parse diff --git a/lib/rainbows/response.rb b/lib/rainbows/response.rb index 576ff8d..65599e9 100644 --- a/lib/rainbows/response.rb +++ b/lib/rainbows/response.rb @@ -14,7 +14,7 @@ module Rainbows::Response # called after forking def self.setup(klass) Kgio.accept_class = Rainbows::Client - 0 == Rainbows.keepalive_timeout and + 0 == Rainbows.server.keepalive_timeout and Rainbows::HttpParser.keepalive_requests = 0 end diff --git a/lib/rainbows/revactor/client.rb b/lib/rainbows/revactor/client.rb index c587589..c1cb7aa 100644 --- a/lib/rainbows/revactor/client.rb +++ b/lib/rainbows/revactor/client.rb @@ -4,8 +4,8 @@ require 'fcntl' class Rainbows::Revactor::Client autoload :TeeSocket, 'rainbows/revactor/client/tee_socket' RD_ARGS = {} - Rainbows.keepalive_timeout > 0 and - RD_ARGS[:timeout] = Rainbows.keepalive_timeout + Rainbows.server.keepalive_timeout > 0 and + RD_ARGS[:timeout] = Rainbows.server.keepalive_timeout attr_reader :kgio_addr def initialize(client) diff --git a/lib/rainbows/xepoll_thread_pool/client.rb b/lib/rainbows/xepoll_thread_pool/client.rb index 1bfb1c2..f871e56 100644 --- a/lib/rainbows/xepoll_thread_pool/client.rb +++ b/lib/rainbows/xepoll_thread_pool/client.rb @@ -3,7 +3,7 @@ # FIXME: lots of duplication from xepolll_thread_spawn/client module Rainbows::XEpollThreadPool::Client - HBUFSIZ = Rainbows.client_header_buffer_size + Rainbows.config!(self, :keepalive_timeout, :client_header_buffer_size) N = Raindrops.new(1) ACCEPTORS = Rainbows::HttpServer::LISTENERS.dup extend Rainbows::WorkerYield @@ -66,7 +66,7 @@ module Rainbows::XEpollThreadPool::Client def self.expire return if ((now = Time.now) - @@last_expire) < 1.0 - if (ot = Rainbows.keepalive_timeout) >= 0 + if (ot = KEEPALIVE_TIMEOUT) >= 0 ot = now - ot defer = [] LOCK.synchronize do @@ -101,7 +101,7 @@ module Rainbows::XEpollThreadPool::Client end def epoll_run(buf) - case kgio_tryread(HBUFSIZ, buf) + case kgio_tryread(CLIENT_HEADER_BUFFER_SIZE, buf) when :wait_readable return kato_set when String diff --git a/lib/rainbows/xepoll_thread_spawn/client.rb b/lib/rainbows/xepoll_thread_spawn/client.rb index 049d4e1..f8fc191 100644 --- a/lib/rainbows/xepoll_thread_spawn/client.rb +++ b/lib/rainbows/xepoll_thread_spawn/client.rb @@ -1,7 +1,7 @@ # -*- encoding: binary -*- # :stopdoc: module Rainbows::XEpollThreadSpawn::Client - HBUFSIZ = Rainbows.client_header_buffer_size + Rainbows.config!(self, :keepalive_timeout, :client_header_buffer_size) N = Raindrops.new(1) ACCEPTORS = Rainbows::HttpServer::LISTENERS.dup extend Rainbows::WorkerYield @@ -55,7 +55,7 @@ module Rainbows::XEpollThreadSpawn::Client def self.expire return if ((now = Time.now) - @@last_expire) < 1.0 - if (ot = Rainbows.keepalive_timeout) >= 0 + if (ot = KEEPALIVE_TIMEOUT) >= 0 ot = now - ot defer = [] LOCK.synchronize do @@ -85,7 +85,7 @@ module Rainbows::XEpollThreadSpawn::Client end def epoll_run(buf) - case kgio_tryread(HBUFSIZ, buf) + case kgio_tryread(CLIENT_HEADER_BUFFER_SIZE, buf) when :wait_readable return kato_set when String @@ -105,7 +105,7 @@ module Rainbows::XEpollThreadSpawn::Client def pipeline_ready(hp) hp.parse and return true - case buf = kgio_tryread(HBUFSIZ) + case buf = kgio_tryread(CLIENT_HEADER_BUFFER_SIZE) when :wait_readable kato_set return false -- cgit v1.2.3-24-ge0c7