class PhusionPassenger::AbstractRequestHandler
and sends it to the request handler.
The web server transforms the HTTP request to the aforementioned format,
NUL = “x00”
notnull ::= “x01” | “x02” | “x02” | … | “xFF”
value ::= notnull+
name ::= notnull+
header ::= name NUL value NUL
headers ::= header*
grammar:
HTTP headers are transformed to a format that satisfies the following
- The verbatim (untransformed) HTTP request body.
- The transformed HTTP headers.
headers.
- A 32-bit big-endian integer, containing the size of the transformed
The format consists of 3 parts:
is based on CGI, and is similar to that of SCGI.
representation do not conform to RFC 2616. Instead, the request format
Incoming “HTTP requests” are not true HTTP requests, i.e. their binary
== Request format
server’s using ApplicationPoolServer instead of StandardApplicationPool.
* It might also be passed to the ApplicationPoolServerExecutable, if the web
as well. This works even if the web server gets killed by SIGKILL.
knows that the web server has exited, and so the request handler will exit
check whether the other side of the pipe has been closed. If so, then it
the readable part of the pipe. AbstractRequestHandler will continuously
server will own that part of the pipe, while AbstractRequestHandler owns
of the pipe will be passed to the web server* via a Unix socket, and the web
This is implemented by using a so-called _owner pipe_. The writable part
we want the request handler to exit if the web server has also exited.
Because only the web server communicates directly with a request handler,
=== Owner pipes
in this section.
administrator maintenance overhead. These decisions are documented
Some design decisions are made because we want to decrease system
== Design decisions
of request handlers for Rails and Rack.
AbstractRequestHandler is an abstract base class for easing the implementation
response back to the HTTP client.
RoR application are forwarded to the web server, which, in turn, sends the
to the request handler by the web server. HTTP responses generated by the
currently loaded Ruby on Rails application. HTTP requests are forwarded
The request handler’s job is to process incoming HTTP requests using the
request dispatcher (i.e. either Rails’s Dispatcher class or Rack).
The request handler is the layer which connects Apache with the underlying application’s
def self.determine_passenger_header
def self.determine_passenger_header header = "Phusion Passenger (mod_rails/mod_rack) #{VERSION_STRING}" if File.exist?("#{File.dirname(__FILE__)}/../../enterprisey.txt") || File.exist?("/etc/passenger_enterprisey.txt") header << ", Enterprise Edition" end return header end
def accept_connection
def accept_connection ios = select([@socket, @owner_pipe, @graceful_termination_pipe[0]]).first if ios.include?(@socket) client = @socket.accept client.close_on_exec! # Some people report that sometimes their Ruby (MRI/REE) # processes get stuck with 100% CPU usage. Upon further # inspection with strace, it turns out that these Ruby # processes are continuously calling lseek() on a socket, # which of course returns ESPIPE as error. gdb reveals # lseek() is called by fwrite(), which in turn is called # by rb_fwrite(). The affected socket is the # AbstractRequestHandler client socket. # # I inspected the MRI source code and didn't find # anything that would explain this behavior. This makes # me think that it's a glibc bug, but that's very # unlikely. # # The rb_fwrite() implementation takes an entirely # different code path if I set 'sync' to true: it will # skip fwrite() and use write() instead. So here we set # 'sync' to true in the hope that this will work around # the problem. client.sync = true # We monkeypatch the 'sync=' method to a no-op so that # sync mode can't be disabled. def client.sync=(value) end # The real input stream is not seekable (calling _seek_ # or _rewind_ on it will raise an exception). But some # frameworks (e.g. Merb) call _rewind_ if the object # responds to it. So we simply undefine _seek_ and # _rewind_. client.instance_eval do undef seek if respond_to?(:seek) undef rewind if respond_to?(:rewind) end return client else # The other end of the owner pipe has been closed, or the # graceful termination pipe has been closed. This is our # call to gracefully terminate (after having processed all # incoming requests). return nil end end
def cleanup
If the main loop was started by #start_main_loop_thread, then this method
be called after the main loop has exited.
If the main loop was started by #main_loop, then this method may only
Clean up temporary stuff created by the request handler.
def cleanup if @main_loop_thread @main_loop_thread.raise(Interrupt.new("Cleaning up")) @main_loop_thread.join end @socket.close rescue nil @owner_pipe.close rescue nil File.unlink(@socket_name) rescue nil end
def create_tcp_socket
def create_tcp_socket # We use "127.0.0.1" as address in order to force # TCPv4 instead of TCPv6. @socket = TCPServer.new('127.0.0.1', 0) @socket.listen(BACKLOG_SIZE) @socket_name = "127.0.0.1:#{@socket.addr[1]}" @socket_type = "tcp" end
def create_unix_socket_on_filesystem
def create_unix_socket_on_filesystem done = false while !done begin if defined?(NativeSupport) unix_path_max = NativeSupport::UNIX_PATH_MAX else unix_path_max = 100 end @socket_name = "#{passenger_tmpdir}/backends/backend.#{generate_random_id(:base64)}" @socket_name = @socket_name.slice(0, unix_path_max - 1) @socket = UNIXServer.new(@socket_name) @socket.listen(BACKLOG_SIZE) @socket_type = "unix" File.chmod(0600, @socket_name) # The SpawnManager class will set tighter permissions on the # socket later on. See sendSpawnCommand in SpawnManager.h. done = true rescue Errno::EADDRINUSE # Do nothing, try again with another name. end end end
def generate_random_id(method)
Generate a long, cryptographically secure random ID string, which
def generate_random_id(method) case method when :base64 require 'base64' unless defined?(Base64) data = Base64.encode64(File.read("/dev/urandom", 64)) data.gsub!("\n", '') data.gsub!("+", '') data.gsub!("/", '') data.gsub!(/==$/, '') when :hex data = File.read("/dev/urandom", 64).unpack('H*')[0] end return data end
def initialize(owner_pipe, options = {})
Additionally, the following options may be given:
+owner_pipe+ must be the readable part of a pipe IO object.
Create a new RequestHandler with the given owner pipe.
def initialize(owner_pipe, options = {}) if should_use_unix_sockets? create_unix_socket_on_filesystem else create_tcp_socket end @socket.close_on_exec! @owner_pipe = owner_pipe @previous_signal_handlers = {} @main_loop_thread_lock = Mutex.new @main_loop_thread_cond = ConditionVariable.new @memory_limit = options["memory_limit"] || 0 @iterations = 0 @processed_requests = 0 end
def install_useful_signal_handlers
def install_useful_signal_handlers trappable_signals = Signal.list_trappable trap(SOFT_TERMINATION_SIGNAL) do @graceful_termination_pipe[1].close rescue nil end if trappable_signals.has_key?(SOFT_TERMINATION_SIGNAL.sub(/^SIG/, '')) trap('ABRT') do raise SignalException, "SIGABRT" end if trappable_signals.has_key?('ABRT') trap('QUIT') do if Kernel.respond_to?(:caller_for_all_threads) output = "========== Process #{Process.pid}: backtrace dump ==========\n" caller_for_all_threads.each_pair do |thread, stack| output << ("-" * 60) << "\n" output << "# Thread: #{thread.inspect}, " if thread == Thread.main output << "[main thread], " else output << "[current thread], " end output << "alive = #{thread.alive?}\n" output << ("-" * 60) << "\n" output << " " << stack.join("\n ") output << "\n\n" end else output = "========== Process #{Process.pid}: backtrace dump ==========\n" output << ("-" * 60) << "\n" output << "# Current thread: #{Thread.current.inspect}\n" output << ("-" * 60) << "\n" output << " " << caller.join("\n ") end STDERR.puts(output) STDERR.flush end if trappable_signals.has_key?('QUIT') end
def main_loop
def main_loop reset_signal_handlers begin @graceful_termination_pipe = IO.pipe @graceful_termination_pipe[0].close_on_exec! @graceful_termination_pipe[1].close_on_exec! @main_loop_thread_lock.synchronize do @main_loop_running = true @main_loop_thread_cond.broadcast end install_useful_signal_handlers while true @iterations += 1 client = accept_connection if client.nil? break end begin headers, input = parse_request(client) if headers if headers[REQUEST_METHOD] == PING process_ping(headers, input, client) else process_request(headers, input, client) end end rescue IOError, SocketError, SystemCallError => e print_exception("Passenger RequestHandler", e) ensure # 'input' is the same as 'client' so we don't # need to close that. # The 'close_write' here prevents forked child # processes from unintentionally keeping the # connection open. client.close_write rescue nil client.close rescue nil end @processed_requests += 1 end rescue EOFError # Exit main loop. rescue Interrupt # Exit main loop. rescue SignalException => signal if signal.message != HARD_TERMINATION_SIGNAL && signal.message != SOFT_TERMINATION_SIGNAL raise end ensure @graceful_termination_pipe[0].close rescue nil @graceful_termination_pipe[1].close rescue nil revert_signal_handlers @main_loop_thread_lock.synchronize do @main_loop_running = false @main_loop_thread_cond.broadcast end end end
def main_loop_running?
def main_loop_running? return @main_loop_running end
def parse_request(socket)
reading HTTP POST data.
the request headers, while _input_stream_ is an IO object for
a pair [headers, input_stream]. _headers_ is a Hash containing
Read the next request from the given socket, and return
def parse_request(socket) channel = MessageChannel.new(socket) headers_data = channel.read_scalar(MAX_HEADER_SIZE) if headers_data.nil? return end headers = Hash[*headers_data.split(NULL)] headers[CONTENT_LENGTH] = headers[HTTP_CONTENT_LENGTH] return [headers, socket] rescue SecurityError => e STDERR.puts("*** Passenger RequestHandler: HTTP header size exceeded maximum.") STDERR.flush print_exception("Passenger RequestHandler", e) end
def process_ping(env, input, output)
def process_ping(env, input, output) output.write("pong") end
def reset_signal_handlers
special handlers for a few signals. The previous signal handlers
Reset signal handlers to their default handler, and install some
def reset_signal_handlers Signal.list_trappable.each_key do |signal| begin prev_handler = trap(signal, DEFAULT) if prev_handler != DEFAULT @previous_signal_handlers[signal] = prev_handler end rescue ArgumentError # Signal cannot be trapped; ignore it. end end trap('HUP', IGNORE) end
def revert_signal_handlers
def revert_signal_handlers @previous_signal_handlers.each_pair do |signal, handler| trap(signal, handler) end end
def should_use_unix_sockets?
def should_use_unix_sockets? # There seems to be a bug in MacOS X w.r.t. Unix sockets. # When the Unix socket subsystem is under high stress, a # recv()/read() on a Unix socket can return 0 even when EOF is # not reached. We work around this by using TCP sockets on # MacOS X. return RUBY_PLATFORM !~ /darwin/ end
def start_main_loop_thread
def start_main_loop_thread @main_loop_thread = Thread.new do main_loop end @main_loop_thread_lock.synchronize do while !@main_loop_running @main_loop_thread_cond.wait(@main_loop_thread_lock) end end end