configurator: provide stream_input (true|false) option
[unicorn.git] / lib / unicorn / configurator.rb
blob99a3c041edfd809786e283539985cb97bcf196df
1 require 'socket'
2 require 'logger'
4 module Unicorn
6   # Implements a simple DSL for configuring a unicorn server.
7   #
8   # Example (when used with the unicorn config file):
9   #   worker_processes 4
10   #   listen '/tmp/my_app.sock', :backlog => 1
11   #   listen '0.0.0.0:9292'
12   #   timeout 10
13   #   pid "/tmp/my_app.pid"
14   #   after_fork do |server,worker|
15   #     server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
16   #   end
17   class Configurator
18     # The default logger writes its output to $stderr
19     DEFAULT_LOGGER = Logger.new($stderr) unless defined?(DEFAULT_LOGGER)
21     # Default settings for Unicorn
22     DEFAULTS = {
23       :timeout => 60,
24       :listeners => [],
25       :logger => DEFAULT_LOGGER,
26       :worker_processes => 1,
27       :after_fork => lambda { |server, worker|
28           server.logger.info("worker=#{worker.nr} spawned pid=#{$$}")
30           # per-process listener ports for debugging/admin:
31           # "rescue nil" statement is needed because USR2 will
32           # cause the master process to reexecute itself and the
33           # per-worker ports can be taken, necessitating another
34           # HUP after QUIT-ing the original master:
35           # server.listen("127.0.0.1:#{8081 + worker.nr}") rescue nil
36         },
37       :before_fork => lambda { |server, worker|
38           server.logger.info("worker=#{worker.nr} spawning...")
39         },
40       :before_exec => lambda { |server|
41           server.logger.info("forked child re-executing...")
42         },
43       :pid => nil,
44       :preload_app => false,
45       :stderr_path => nil,
46       :stdout_path => nil,
47       :stream_input => false,
48     }
50     attr_reader :config_file #:nodoc:
52     def initialize(defaults = {}) #:nodoc:
53       @set = Hash.new(:unset)
54       use_defaults = defaults.delete(:use_defaults)
55       @config_file = defaults.delete(:config_file)
56       @config_file.freeze
57       @set.merge!(DEFAULTS) if use_defaults
58       defaults.each { |key, value| self.send(key, value) }
59       reload
60     end
62     def reload #:nodoc:
63       instance_eval(File.read(@config_file)) if @config_file
64     end
66     def commit!(server, options = {}) #:nodoc:
67       skip = options[:skip] || []
68       stream_input = @set.delete(:stream_input)
69       unless stream_input.nil?
70         Unicorn::HttpRequest::DEFAULTS[Const::STREAM_INPUT] = stream_input
71       end
72       @set.each do |key, value|
73         value == :unset and next
74         skip.include?(key) and next
75         setter = "#{key}="
76         if server.respond_to?(setter)
77           server.send(setter, value)
78         else
79           server.instance_variable_set("@#{key}", value)
80         end
81       end
82     end
84     def [](key) # :nodoc:
85       @set[key]
86     end
88     # sets object to the +new+ Logger-like object.  The new logger-like
89     # object must respond to the following methods:
90     #  +debug+, +info+, +warn+, +error+, +fatal+, +close+
91     def logger(new)
92       %w(debug info warn error fatal close).each do |m|
93         new.respond_to?(m) and next
94         raise ArgumentError, "logger=#{new} does not respond to method=#{m}"
95       end
97       @set[:logger] = new
98     end
100     # sets after_fork hook to a given block.  This block will be called by
101     # the worker after forking.  The following is an example hook which adds
102     # a per-process listener to every worker:
103     #
104     #  after_fork do |server,worker|
105     #    # per-process listener ports for debugging/admin:
106     #    # "rescue nil" statement is needed because USR2 will
107     #    # cause the master process to reexecute itself and the
108     #    # per-worker ports can be taken, necessitating another
109     #    # HUP after QUIT-ing the original master:
110     #    server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
111     #
112     #    # drop permissions to "www-data" in the worker
113     #    # generally there's no reason to start Unicorn as a priviledged user
114     #    # as it is not recommended to expose Unicorn to public clients.
115     #    uid, gid = Process.euid, Process.egid
116     #    user, group = 'www-data', 'www-data'
117     #    target_uid = Etc.getpwnam(user).uid
118     #    target_gid = Etc.getgrnam(group).gid
119     #    worker.tempfile.chown(target_uid, target_gid)
120     #    if uid != target_uid || gid != target_gid
121     #      Process.initgroups(user, target_gid)
122     #      Process::GID.change_privilege(target_gid)
123     #      Process::UID.change_privilege(target_uid)
124     #    end
125     #  end
126     def after_fork(*args, &block)
127       set_hook(:after_fork, block_given? ? block : args[0])
128     end
130     # sets before_fork got be a given Proc object.  This Proc
131     # object will be called by the master process before forking
132     # each worker.
133     def before_fork(*args, &block)
134       set_hook(:before_fork, block_given? ? block : args[0])
135     end
137     # sets the before_exec hook to a given Proc object.  This
138     # Proc object will be called by the master process right
139     # before exec()-ing the new unicorn binary.  This is useful
140     # for freeing certain OS resources that you do NOT wish to
141     # share with the reexeced child process.
142     # There is no corresponding after_exec hook (for obvious reasons).
143     def before_exec(*args, &block)
144       set_hook(:before_exec, block_given? ? block : args[0], 1)
145     end
147     # sets the timeout of worker processes to +seconds+.  Workers
148     # handling the request/app.call/response cycle taking longer than
149     # this time period will be forcibly killed (via SIGKILL).  This
150     # timeout is enforced by the master process itself and not subject
151     # to the scheduling limitations by the worker process.  Due the
152     # low-complexity, low-overhead implementation, timeouts of less
153     # than 3.0 seconds can be considered inaccurate and unsafe.
154     def timeout(seconds)
155       Numeric === seconds or raise ArgumentError,
156                                   "not numeric: timeout=#{seconds.inspect}"
157       seconds >= 3 or raise ArgumentError,
158                                   "too low: timeout=#{seconds.inspect}"
159       @set[:timeout] = seconds
160     end
162     # sets the current number of worker_processes to +nr+.  Each worker
163     # process will serve exactly one client at a time.
164     def worker_processes(nr)
165       Integer === nr or raise ArgumentError,
166                              "not an integer: worker_processes=#{nr.inspect}"
167       nr >= 0 or raise ArgumentError,
168                              "not non-negative: worker_processes=#{nr.inspect}"
169       @set[:worker_processes] = nr
170     end
172     # sets listeners to the given +addresses+, replacing or augmenting the
173     # current set.  This is for the global listener pool shared by all
174     # worker processes.  For per-worker listeners, see the after_fork example
175     # This is for internal API use only, do not use it in your Unicorn
176     # config file.  Use listen instead.
177     def listeners(addresses) # :nodoc:
178       Array === addresses or addresses = Array(addresses)
179       addresses.map! { |addr| expand_addr(addr) }
180       @set[:listeners] = addresses
181     end
183     # adds an +address+ to the existing listener set.
184     #
185     # The following options may be specified (but are generally not needed):
186     #
187     # +backlog+: this is the backlog of the listen() syscall.
188     #
189     # Some operating systems allow negative values here to specify the
190     # maximum allowable value.  In most cases, this number is only
191     # recommendation and there are other OS-specific tunables and
192     # variables that can affect this number.  See the listen(2)
193     # syscall documentation of your OS for the exact semantics of
194     # this.
195     #
196     # If you are running unicorn on multiple machines, lowering this number
197     # can help your load balancer detect when a machine is overloaded
198     # and give requests to a different machine.
199     #
200     # Default: 1024
201     #
202     # +rcvbuf+, +sndbuf+: maximum send and receive buffer sizes of sockets
203     #
204     # These correspond to the SO_RCVBUF and SO_SNDBUF settings which
205     # can be set via the setsockopt(2) syscall.  Some kernels
206     # (e.g. Linux 2.4+) have intelligent auto-tuning mechanisms and
207     # there is no need (and it is sometimes detrimental) to specify them.
208     #
209     # See the socket API documentation of your operating system
210     # to determine the exact semantics of these settings and
211     # other operating system-specific knobs where they can be
212     # specified.
213     #
214     # Defaults: operating system defaults
215     def listen(address, opt = { :backlog => 1024 })
216       address = expand_addr(address)
217       if String === address
218         Hash === @set[:listener_opts] or
219           @set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
220         [ :backlog, :sndbuf, :rcvbuf ].each do |key|
221           value = opt[key] or next
222           Integer === value or
223             raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
224         end
225         @set[:listener_opts][address].merge!(opt)
226       end
228       @set[:listeners] = [] unless Array === @set[:listeners]
229       @set[:listeners] << address
230     end
232     # sets the +path+ for the PID file of the unicorn master process
233     def pid(path); set_path(:pid, path); end
235     # Enabling this preloads an application before forking worker
236     # processes.  This allows memory savings when using a
237     # copy-on-write-friendly GC but can cause bad things to happen when
238     # resources like sockets are opened at load time by the master
239     # process and shared by multiple children.  People enabling this are
240     # highly encouraged to look at the before_fork/after_fork hooks to
241     # properly close/reopen sockets.  Files opened for logging do not
242     # have to be reopened as (unbuffered-in-userspace) files opened with
243     # the File::APPEND flag are written to atomically on UNIX.
244     #
245     # In addition to reloading the unicorn-specific config settings,
246     # SIGHUP will reload application code in the working
247     # directory/symlink when workers are gracefully restarted.
248     def preload_app(bool)
249       case bool
250       when TrueClass, FalseClass
251         @set[:preload_app] = bool
252       else
253         raise ArgumentError, "preload_app=#{bool.inspect} not a boolean"
254       end
255     end
257     # Allow applications to stream input as it is being read from the
258     # network directly to the application.  Enabling this can allow
259     # real-time processing of request bodies as it is being sent by
260     # the client, useful for things like upload progress notification
261     # and tunneling arbitrary stream protocols via bidirectional chunked
262     # transfer encoding.
263     # This may not work with all applications because some broken
264     # applications assume env['rack.input'].read(size) always returns
265     # the requested amount of data.  This causes env['rack.input']#read
266     # to provide IO#readpartial semantics instead.  Some applications
267     # may also fully receive an input and never attempt to process it,
268     # causing clients confusion when they receive a response after
269     # only a partial request has been sent.
270     def stream_input(bool)
271       case bool
272       when TrueClass, FalseClass
273         @set[:stream_input] = bool
274       else
275         raise ArgumentError, "stream_input=#{bool.inspect} not a boolean"
276       end
277     end
279     # Allow redirecting $stderr to a given path.  Unlike doing this from
280     # the shell, this allows the unicorn process to know the path its
281     # writing to and rotate the file if it is used for logging.  The
282     # file will be opened with the File::APPEND flag and writes
283     # synchronized to the kernel (but not necessarily to _disk_) so
284     # multiple processes can safely append to it.
285     def stderr_path(path)
286       set_path(:stderr_path, path)
287     end
289     # Same as stderr_path, except for $stdout
290     def stdout_path(path)
291       set_path(:stdout_path, path)
292     end
294     private
296     def set_path(var, path) #:nodoc:
297       case path
298       when NilClass
299       when String
300         path = File.expand_path(path)
301         File.writable?(File.dirname(path)) or \
302                raise ArgumentError, "directory for #{var}=#{path} not writable"
303       else
304         raise ArgumentError
305       end
306       @set[var] = path
307     end
309     def set_hook(var, my_proc, req_arity = 2) #:nodoc:
310       case my_proc
311       when Proc
312         arity = my_proc.arity
313         (arity == req_arity) or \
314           raise ArgumentError,
315                 "#{var}=#{my_proc.inspect} has invalid arity: " \
316                 "#{arity} (need #{req_arity})"
317       when NilClass
318         my_proc = DEFAULTS[var]
319       else
320         raise ArgumentError, "invalid type: #{var}=#{my_proc.inspect}"
321       end
322       @set[var] = my_proc
323     end
325     # expands "unix:path/to/foo" to a socket relative to the current path
326     # expands pathnames of sockets if relative to "~" or "~username"
327     # expands "*:port and ":port" to "0.0.0.0:port"
328     def expand_addr(address) #:nodoc
329       return "0.0.0.0:#{address}" if Integer === address
330       return address unless String === address
332       case address
333       when %r{\Aunix:(.*)\z}
334         File.expand_path($1)
335       when %r{\A~}
336         File.expand_path(address)
337       when %r{\A(?:\*:)?(\d+)\z}
338         "0.0.0.0:#$1"
339       when %r{\A(.*):(\d+)\z}
340         # canonicalize the name
341         packed = Socket.pack_sockaddr_in($2.to_i, $1)
342         Socket.unpack_sockaddr_in(packed).reverse!.join(':')
343       else
344         address
345       end
346     end
348   end