The request handler is the layer which connects Apache with the underlying
application’s request dispatcher (i.e. either Rails’s
Dispatcher class or Rack). The request
handler’s job is to process incoming HTTP requests using the
currently loaded Ruby on Rails application. HTTP requests are forwarded to
the request handler by the web server. HTTP responses generated by the RoR
application are forwarded to the web server, which, in turn, sends the
response back to the HTTP client.
AbstractRequestHandler is an
abstract base class for easing the implementation of request handlers for
Rails and Rack.
Design decisions
Some design decisions are made because we want to decrease system
administrator maintenance overhead. These decisions are documented in this
section.
Owner pipes
Because only the web server communicates directly with a request handler,
we want the request handler to exit if the web server has also exited. This
is implemented by using a so-called _owner pipe_. The writable part of the
pipe will be passed to the web server* via a Unix socket, and the web
server will own that part of the pipe, while AbstractRequestHandler owns the
readable part of the pipe. AbstractRequestHandler will
continuously check whether the other side of the pipe has been closed. If
so, then it knows that the web server has exited, and so the request
handler will exit as well. This works even if the web server gets killed by
SIGKILL.
Request format
Incoming “HTTP requests” are not true HTTP requests, i.e. their
binary representation do not conform to RFC 2616. Instead, the request
format is based on CGI, and is similar to that of SCGI.
The format consists of 3 parts:
A 32-bit big-endian integer, containing the size of the transformed
headers.
The transformed HTTP headers.
The verbatim (untransformed) HTTP request body.
HTTP headers are transformed to a format that satisfies the following
grammar:
headers ::= header*
header ::= name NUL value NUL
name ::= notnull+
value ::= notnull+
notnull ::= "\x01" | "\x02" | "\x02" | ... | "\xFF"
NUL = "\x00"
The web server transforms the HTTP request to the aforementioned format,
and sends it to the request handler.
accept_connection()
click to toggle source
368: def accept_connection
369: ios = select([@socket, @owner_pipe, @graceful_termination_pipe[0]]).first
370: if ios.include?(@socket)
371: client = @socket.accept
372: client.close_on_exec!
373:
374:
375:
376:
377:
378:
379:
380:
381:
382:
383:
384:
385:
386:
387:
388:
389:
390:
391:
392:
393: client.sync = true
394:
395:
396:
397: def client.sync=(value)
398: end
399:
400:
401:
402:
403:
404:
405: client.instance_eval do
406: undef seek if respond_to?(:seek)
407: undef rewind if respond_to?(:rewind)
408: end
409:
410:
411:
412:
413: return client
414: else
415:
416:
417:
418:
419: return nil
420: end
421: end
create_tcp_socket()
click to toggle source
295: def create_tcp_socket
296:
297:
298: @socket = TCPServer.new('127.0.0.1', 0)
299: @socket.listen(BACKLOG_SIZE)
300: @socket_name = "127.0.0.1:#{@socket.addr[1]}"
301: @socket_type = "tcp"
302: end
create_unix_socket_on_filesystem()
click to toggle source
269: def create_unix_socket_on_filesystem
270: done = false
271: while !done
272: begin
273: if defined?(NativeSupport)
274: unix_path_max = NativeSupport::UNIX_PATH_MAX
275: else
276: unix_path_max = 100
277: end
278: @socket_name = "#{passenger_tmpdir}/backends/backend.#{generate_random_id(:base64)}"
279: @socket_name = @socket_name.slice(0, unix_path_max - 1)
280: @socket = UNIXServer.new(@socket_name)
281: @socket.listen(BACKLOG_SIZE)
282: @socket_type = "unix"
283: File.chmod(0600, @socket_name)
284:
285:
286:
287:
288: done = true
289: rescue Errno::EADDRINUSE
290:
291: end
292: end
293: end
generate_random_id(method)
click to toggle source
Generate a long, cryptographically secure random ID string, which is also a
valid filename.
449: def generate_random_id(method)
450: case method
451: when :base64
452: require 'base64' unless defined?(Base64)
453: data = Base64.encode64(File.read("/dev/urandom", 64))
454: data.gsub!("\n", '')
455: data.gsub!("+", '')
456: data.gsub!("/", '')
457: data.gsub!(/==$/, '')
458: when :hex
459: data = File.read("/dev/urandom", 64).unpack('H*')[0]
460: end
461: return data
462: end
install_useful_signal_handlers()
click to toggle source
322: def install_useful_signal_handlers
323: trappable_signals = Signal.list_trappable
324:
325: trap(SOFT_TERMINATION_SIGNAL) do
326: @graceful_termination_pipe[1].close rescue nil
327: end if trappable_signals.has_key?(SOFT_TERMINATION_SIGNAL.sub(/^SIG/, ''))
328:
329: trap('ABRT') do
330: raise SignalException, "SIGABRT"
331: end if trappable_signals.has_key?('ABRT')
332:
333: trap('QUIT') do
334: if Kernel.respond_to?(:caller_for_all_threads)
335: output = "========== Process #{Process.pid}: backtrace dump ==========\n"
336: caller_for_all_threads.each_pair do |thread, stack|
337: output << ("-" * 60) << "\n"
338: output << "# Thread: #{thread.inspect}, "
339: if thread == Thread.main
340: output << "[main thread], "
341: end
342: if thread == Thread.current
343: output << "[current thread], "
344: end
345: output << "alive = #{thread.alive?}\n"
346: output << ("-" * 60) << "\n"
347: output << " " << stack.join("\n ")
348: output << "\n\n"
349: end
350: else
351: output = "========== Process #{Process.pid}: backtrace dump ==========\n"
352: output << ("-" * 60) << "\n"
353: output << "# Current thread: #{Thread.current.inspect}\n"
354: output << ("-" * 60) << "\n"
355: output << " " << caller.join("\n ")
356: end
357: STDERR.puts(output)
358: STDERR.flush
359: end if trappable_signals.has_key?('QUIT')
360: end
parse_request(socket)
click to toggle source
Read the next request from the given socket, and return a pair [headers,
input_stream]. headers is a Hash containing the request headers,
while input_stream is an IO object for
reading HTTP POST data.
Returns nil if end-of-stream was encountered.
429: def parse_request(socket)
430: channel = MessageChannel.new(socket)
431: headers_data = channel.read_scalar(MAX_HEADER_SIZE)
432: if headers_data.nil?
433: return
434: end
435: headers = Hash[*headers_data.split(NULL)]
436: return [headers, socket]
437: rescue SecurityError => e
438: STDERR.puts("*** Passenger RequestHandler: HTTP header size exceeded maximum.")
439: STDERR.flush
440: print_exception("Passenger RequestHandler", e)
441: end
process_ping(env, input, output)
click to toggle source
443: def process_ping(env, input, output)
444: output.write("pong")
445: end
reset_signal_handlers()
click to toggle source
Reset signal handlers to their default handler, and install some special
handlers for a few signals. The previous signal handlers will be put back
by calling revert_signal_handlers.
307: def reset_signal_handlers
308: Signal.list_trappable.each_key do |signal|
309: begin
310: prev_handler = trap(signal, DEFAULT)
311: if prev_handler != DEFAULT
312: @previous_signal_handlers[signal] = prev_handler
313: end
314: rescue ArgumentError
315:
316: end
317: end
318: trap('HUP', IGNORE)
319: PhusionPassenger.call_event(:after_installing_signal_handlers)
320: end
revert_signal_handlers()
click to toggle source
362: def revert_signal_handlers
363: @previous_signal_handlers.each_pair do |signal, handler|
364: trap(signal, handler)
365: end
366: end
should_use_unix_sockets?()
click to toggle source
260: def should_use_unix_sockets?
261:
262:
263:
264:
265:
266: return RUBY_PLATFORM !~ /darwin/
267: end