diff options
Diffstat (limited to 'lib/unicorn/configurator.rb')
-rw-r--r-- | lib/unicorn/configurator.rb | 332 |
1 files changed, 209 insertions, 123 deletions
diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb index fb37c56..b6ad022 100644 --- a/lib/unicorn/configurator.rb +++ b/lib/unicorn/configurator.rb @@ -8,14 +8,26 @@ require 'logger' # example configuration files. An example config file for use with # nginx is also available at # http://unicorn.bogomips.org/examples/nginx.conf -class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) +# +# See the link:/TUNING.html document for more information on tuning unicorn. +class Unicorn::Configurator + include Unicorn + + # :stopdoc: + attr_accessor :set, :config_file, :after_reload + # used to stash stuff for deferred processing of cli options in # config.ru after "working_directory" is bound. Do not rely on # this being around later on... - RACKUP = {} # :nodoc: + RACKUP = { + :daemonize => false, + :host => Unicorn::Const::DEFAULT_HOST, + :port => Unicorn::Const::DEFAULT_PORT, + :set_listener => false, + :options => { :listeners => [] } + } # Default settings for Unicorn - # :stopdoc: DEFAULTS = { :timeout => 60, :logger => Logger.new($stderr), @@ -31,6 +43,9 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) }, :pid => nil, :preload_app => false, + :rewindable_input => true, # for Rack 2.x: (Rack::VERSION[0] <= 1), + :client_body_buffer_size => Unicorn::Const::MAX_BODY, + :trust_x_forwarded => true, } #:startdoc: @@ -58,6 +73,9 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) parse_rackup_file + RACKUP[:set_listener] and + set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}" + # unicorn_rails creates dirs here after working_directory is bound after_reload.call if after_reload @@ -87,20 +105,24 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) set[key] end - # sets object to the +new+ Logger-like object. The new logger-like + # sets object to the +obj+ Logger-like object. The new Logger-like # object must respond to the following methods: - # +debug+, +info+, +warn+, +error+, +fatal+ + # * debug + # * info + # * warn + # * error + # * fatal # The default Logger will log its output to the path specified # by +stderr_path+. If you're running Unicorn daemonized, then # you must specify a path to prevent error messages from going # to /dev/null. - def logger(new) + def logger(obj) %w(debug info warn error fatal).each do |m| - new.respond_to?(m) and next - raise ArgumentError, "logger=#{new} does not respond to method=#{m}" + obj.respond_to?(m) and next + raise ArgumentError, "logger=#{obj} does not respond to method=#{m}" end - set[:logger] = new + set[:logger] = obj end # sets after_fork hook to a given block. This block will be called by @@ -116,11 +138,6 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) # # Existing options for Unicorn::Configurator#listen such as # # :backlog, :rcvbuf, :sndbuf are available here as well. # server.listen(addr, :tries => -1, :delay => 5, :backlog => 128) - # - # # drop permissions to "www-data" in the worker - # # generally there's no reason to start Unicorn as a priviledged user - # # as it is not recommended to expose Unicorn to public clients. - # worker.user('www-data', 'www-data') if Process.euid == 0 # end def after_fork(*args, &block) set_hook(:after_fork, block_given? ? block : args[0]) @@ -168,11 +185,7 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) # server 192.168.0.9:8080 fail_timeout=0; # } def timeout(seconds) - Numeric === seconds or raise ArgumentError, - "not numeric: timeout=#{seconds.inspect}" - seconds >= 3 or raise ArgumentError, - "too low: timeout=#{seconds.inspect}" - set[:timeout] = seconds + set_int(:timeout, seconds, 3) end # sets the current number of worker_processes to +nr+. Each worker @@ -182,11 +195,7 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) # the rest of your Unicorn configuration. See the SIGNALS document # for more information. def worker_processes(nr) - Integer === nr or raise ArgumentError, - "not an integer: worker_processes=#{nr.inspect}" - nr >= 0 or raise ArgumentError, - "not non-negative: worker_processes=#{nr.inspect}" - set[:worker_processes] = nr + set_int(:worker_processes, nr, 1) end # sets listeners to the given +addresses+, replacing or augmenting the @@ -200,131 +209,165 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) set[:listeners] = addresses end - # adds an +address+ to the existing listener set. + # Adds an +address+ to the existing listener set. May be specified more + # than once. +address+ may be an Integer port number for a TCP port, an + # "IP_ADDRESS:PORT" for TCP listeners or a pathname for UNIX domain sockets. + # + # listen 3000 # listen to port 3000 on all TCP interfaces + # listen "127.0.0.1:3000" # listen to port 3000 on the loopback interface + # listen "/tmp/.unicorn.sock" # listen on the given Unix domain socket + # listen "[::1]:3000" # listen to port 3000 on the IPv6 loopback interface # # The following options may be specified (but are generally not needed): # - # +:backlog+: this is the backlog of the listen() syscall. + # [:backlog => number of clients] + # + # This is the backlog of the listen() syscall. + # + # Some operating systems allow negative values here to specify the + # maximum allowable value. In most cases, this number is only + # recommendation and there are other OS-specific tunables and + # variables that can affect this number. See the listen(2) + # syscall documentation of your OS for the exact semantics of + # this. + # + # If you are running unicorn on multiple machines, lowering this number + # can help your load balancer detect when a machine is overloaded + # and give requests to a different machine. + # + # Default: 1024 + # + # [:rcvbuf => bytes, :sndbuf => bytes] + # + # Maximum receive and send buffer sizes (in bytes) of sockets. + # + # These correspond to the SO_RCVBUF and SO_SNDBUF settings which + # can be set via the setsockopt(2) syscall. Some kernels + # (e.g. Linux 2.4+) have intelligent auto-tuning mechanisms and + # there is no need (and it is sometimes detrimental) to specify them. + # + # See the socket API documentation of your operating system + # to determine the exact semantics of these settings and + # other operating system-specific knobs where they can be + # specified. + # + # Defaults: operating system defaults + # + # [:tcp_nodelay => true or false] + # + # Disables Nagle's algorithm on TCP sockets if +true+. # - # Some operating systems allow negative values here to specify the - # maximum allowable value. In most cases, this number is only - # recommendation and there are other OS-specific tunables and - # variables that can affect this number. See the listen(2) - # syscall documentation of your OS for the exact semantics of - # this. + # Setting this to +true+ can make streaming responses in Rails 3.1 + # appear more quickly at the cost of slightly higher bandwidth usage. + # The effect of this option is most visible if nginx is not used, + # but nginx remains highly recommended with \Unicorn. # - # If you are running unicorn on multiple machines, lowering this number - # can help your load balancer detect when a machine is overloaded - # and give requests to a different machine. + # This has no effect on UNIX sockets. # - # Default: 1024 + # Default: +false+ (Nagle's algorithm enabled) in \Unicorn, + # +true+ in Rainbows! # - # +:rcvbuf+, +:sndbuf+: maximum receive and send buffer sizes of sockets + # [:tcp_nopush => true or false] # - # These correspond to the SO_RCVBUF and SO_SNDBUF settings which - # can be set via the setsockopt(2) syscall. Some kernels - # (e.g. Linux 2.4+) have intelligent auto-tuning mechanisms and - # there is no need (and it is sometimes detrimental) to specify them. + # Enables/disables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD # - # See the socket API documentation of your operating system - # to determine the exact semantics of these settings and - # other operating system-specific knobs where they can be - # specified. + # This prevents partial TCP frames from being sent out and reduces + # wakeups in nginx if it is on a different machine. Since \Unicorn + # is only designed for applications that send the response body + # quickly without keepalive, sockets will always be flushed on close + # to prevent delays. # - # Defaults: operating system defaults + # This has no effect on UNIX sockets. # - # +:tcp_nodelay+: disables Nagle's algorithm on TCP sockets + # Default: +true+ in \Unicorn 3.4+, +false+ in Rainbows! # - # This has no effect on UNIX sockets. + # [:tries => Integer] # - # Default: operating system defaults (usually Nagle's algorithm enabled) + # Times to retry binding a socket if it is already in use # - # +:tcp_nopush+: enables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD + # A negative number indicates we will retry indefinitely, this is + # useful for migrations and upgrades when individual workers + # are binding to different ports. # - # This will prevent partial TCP frames from being sent out. - # Enabling +tcp_nopush+ is generally not needed or recommended as - # controlling +tcp_nodelay+ already provides sufficient latency - # reduction whereas Unicorn does not know when the best times are - # for flushing corked sockets. + # Default: 5 # - # This has no effect on UNIX sockets. + # [:delay => seconds] # - # +:tries+: times to retry binding a socket if it is already in use + # Seconds to wait between successive +tries+ # - # A negative number indicates we will retry indefinitely, this is - # useful for migrations and upgrades when individual workers - # are binding to different ports. + # Default: 0.5 seconds # - # Default: 5 + # [:umask => mode] # - # +:delay+: seconds to wait between successive +tries+ + # Sets the file mode creation mask for UNIX sockets. If specified, + # this is usually in octal notation. # - # Default: 0.5 seconds + # Typically UNIX domain sockets are created with more liberal + # file permissions than the rest of the application. By default, + # we create UNIX domain sockets to be readable and writable by + # all local users to give them the same accessibility as + # locally-bound TCP listeners. # - # +:umask+: sets the file mode creation mask for UNIX sockets + # This has no effect on TCP listeners. # - # Typically UNIX domain sockets are created with more liberal - # file permissions than the rest of the application. By default, - # we create UNIX domain sockets to be readable and writable by - # all local users to give them the same accessibility as - # locally-bound TCP listeners. + # Default: 0000 (world-read/writable) # - # This has no effect on TCP listeners. + # [:tcp_defer_accept => Integer] # - # Default: 0 (world read/writable) + # Defer accept() until data is ready (Linux-only) # - # +:tcp_defer_accept:+ defer accept() until data is ready (Linux-only) + # For Linux 2.6.32 and later, this is the number of retransmits to + # defer an accept() for if no data arrives, but the client will + # eventually be accepted after the specified number of retransmits + # regardless of whether data is ready. # - # For Linux 2.6.32 and later, this is the number of retransmits to - # defer an accept() for if no data arrives, but the client will - # eventually be accepted after the specified number of retransmits - # regardless of whether data is ready. + # For Linux before 2.6.32, this is a boolean option, and + # accepts are _always_ deferred indefinitely if no data arrives. + # This is similar to <code>:accept_filter => "dataready"</code> + # under FreeBSD. # - # For Linux before 2.6.32, this is a boolean option, and - # accepts are _always_ deferred indefinitely if no data arrives. - # This is similar to <code>:accept_filter => "dataready"</code> - # under FreeBSD. + # Specifying +true+ is synonymous for the default value(s) below, + # and +false+ or +nil+ is synonymous for a value of zero. # - # Specifying +true+ is synonymous for the default value(s) below, - # and +false+ or +nil+ is synonymous for a value of zero. + # A value of +1+ is a good optimization for local networks + # and trusted clients. For Rainbows! and Zbatery users, a higher + # value (e.g. +60+) provides more protection against some + # denial-of-service attacks. There is no good reason to ever + # disable this with a +zero+ value when serving HTTP. # - # A value of +1+ is a good optimization for local networks - # and trusted clients. For Rainbows! and Zbatery users, a higher - # value (e.g. +60+) provides more protection against some - # denial-of-service attacks. There is no good reason to ever - # disable this with a +zero+ value when serving HTTP. + # Default: 1 retransmit for \Unicorn, 60 for Rainbows! 0.95.0\+ # - # Default: 1 retransmit for \Unicorn, 60 for Rainbows! 0.95.0\+ + # [:accept_filter => String] # - # +:accept_filter: defer accept() until data is ready (FreeBSD-only) + # defer accept() until data is ready (FreeBSD-only) # - # This enables either the "dataready" or (default) "httpready" - # accept() filter under FreeBSD. This is intended as an - # optimization to reduce context switches with common GET/HEAD - # requests. For Rainbows! and Zbatery users, this provides - # some protection against certain denial-of-service attacks, too. + # This enables either the "dataready" or (default) "httpready" + # accept() filter under FreeBSD. This is intended as an + # optimization to reduce context switches with common GET/HEAD + # requests. For Rainbows! and Zbatery users, this provides + # some protection against certain denial-of-service attacks, too. # - # There is no good reason to change from the default. + # There is no good reason to change from the default. # - # Default: "httpready" - def listen(address, opt = {}) + # Default: "httpready" + def listen(address, options = {}) address = expand_addr(address) if String === address [ :umask, :backlog, :sndbuf, :rcvbuf, :tries ].each do |key| - value = opt[key] or next + value = options[key] or next Integer === value or raise ArgumentError, "not an integer: #{key}=#{value.inspect}" end [ :tcp_nodelay, :tcp_nopush ].each do |key| - (value = opt[key]).nil? and next + (value = options[key]).nil? and next TrueClass === value || FalseClass === value or raise ArgumentError, "not boolean: #{key}=#{value.inspect}" end - unless (value = opt[:delay]).nil? + unless (value = options[:delay]).nil? Numeric === value or raise ArgumentError, "not numeric: delay=#{value.inspect}" end - set[:listener_opts][address].merge!(opt) + set[:listener_opts][address].merge!(options) end set[:listeners] << address @@ -362,12 +405,30 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) # cause the master process to exit with an error. def preload_app(bool) - case bool - when TrueClass, FalseClass - set[:preload_app] = bool - else - raise ArgumentError, "preload_app=#{bool.inspect} not a boolean" - end + set_bool(:preload_app, bool) + end + + # Toggles making \env[\"rack.input\"] rewindable. + # Disabling rewindability can improve performance by lowering + # I/O and memory usage for applications that accept uploads. + # Keep in mind that the Rack 1.x spec requires + # \env[\"rack.input\"] to be rewindable, so this allows + # intentionally violating the current Rack 1.x spec. + # + # +rewindable_input+ defaults to +true+ when used with Rack 1.x for + # Rack conformance. When Rack 2.x is finalized, this will most + # likely default to +false+ while still conforming to the newer + # (less demanding) spec. + def rewindable_input(bool) + set_bool(:rewindable_input, bool) + end + + # The maximum size (in +bytes+) to buffer in memory before + # resorting to a temporary file. Default is 112 kilobytes. + # This option has no effect if "rewindable_input" is set to + # +false+. + def client_body_buffer_size(bytes) + set_int(:client_body_buffer_size, bytes, 0) end # Allow redirecting $stderr to a given path. Unlike doing this from @@ -417,6 +478,7 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) # The master process always stays running as the user who started it. # This switch will occur after calling the after_fork hook, and only # if the Worker#user method is not called in the after_fork hook + # +group+ is optional and will not change if unspecified. def user(user, group = nil) # raises ArgumentError on invalid user/group Etc.getpwnam(user) @@ -424,10 +486,22 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) set[:user] = [ user, group ] end + # Sets whether or not the parser will trust X-Forwarded-Proto and + # X-Forwarded-SSL headers and set "rack.url_scheme" to "https" accordingly. + # Rainbows!/Zbatery installations facing untrusted clients directly + # should set this to +false+. This is +true+ by default as Unicorn + # is designed to only sit behind trusted nginx proxies. + # + # This has never been publically documented and is subject to removal + # in future releases. + def trust_x_forwarded(bool) # :nodoc: + set_bool(:trust_x_forwarded, bool) + end + # expands "unix:path/to/foo" to a socket relative to the current path # expands pathnames of sockets if relative to "~" or "~username" # expands "*:port and ":port" to "0.0.0.0:port" - def expand_addr(address) #:nodoc + def expand_addr(address) #:nodoc: return "0.0.0.0:#{address}" if Integer === address return address unless String === address @@ -438,16 +512,27 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload) File.expand_path(address) when %r{\A(?:\*:)?(\d+)\z} "0.0.0.0:#$1" - when %r{\A(.*):(\d+)\z} - # canonicalize the name - packed = Socket.pack_sockaddr_in($2.to_i, $1) - Socket.unpack_sockaddr_in(packed).reverse!.join(':') + when %r{\A\[([a-fA-F0-9:]+)\]\z}, %r/\A((?:\d+\.){3}\d+)\z/ + canonicalize_tcp($1, 80) + when %r{\A\[([a-fA-F0-9:]+)\]:(\d+)\z}, %r{\A(.*):(\d+)\z} + canonicalize_tcp($1, $2.to_i) else address end end private + def set_int(var, n, min) #:nodoc: + Integer === n or raise ArgumentError, "not an integer: #{var}=#{n.inspect}" + n >= min or raise ArgumentError, "too low (< #{min}): #{var}=#{n.inspect}" + set[var] = n + end + + def canonicalize_tcp(addr, port) + packed = Socket.pack_sockaddr_in(port, addr) + port, addr = Socket.unpack_sockaddr_in(packed) + /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}" + end def set_path(var, path) #:nodoc: case path @@ -458,6 +543,15 @@ private end end + def set_bool(var, bool) #:nodoc: + case bool + when true, false + set[var] = bool + else + raise ArgumentError, "#{var}=#{bool.inspect} not a boolean" + end + end + def set_hook(var, my_proc, req_arity = 2) #:nodoc: case my_proc when Proc @@ -495,23 +589,15 @@ private /^#\\(.*)/ =~ File.read(ru) or return RACKUP[:optparse].parse!($1.split(/\s+/)) - # XXX ugly as hell, WILL FIX in 2.x (along with Rainbows!/Zbatery) - host, port, set_listener, options, daemonize = - eval("[ host, port, set_listener, options, daemonize ]", - TOPLEVEL_BINDING) - - # XXX duplicate code from bin/unicorn{,_rails} - set[:listeners] << "#{host}:#{port}" if set_listener - - if daemonize + if RACKUP[:daemonize] # unicorn_rails wants a default pid path, (not plain 'unicorn') if after_reload spid = set[:pid] pid('tmp/pids/unicorn.pid') if spid.nil? || spid == :unset end unless RACKUP[:daemonized] - Unicorn::Launcher.daemonize!(options) - RACKUP[:ready_pipe] = options.delete(:ready_pipe) + Unicorn::Launcher.daemonize!(RACKUP[:options]) + RACKUP[:ready_pipe] = RACKUP[:options].delete(:ready_pipe) end end end |