class PhusionPassenger::AbstractServer
server.stop
server.hello(“Joe”, “Dalton”)
server.start
server = MyServer.new
end
end
send_to_client(“Hello #{first_name} #{last_name}, how are you?”, 1234)
def handle_hello(first_name, last_name)
private
end
puts “In addition, it sent this pointless number: #{pointless_number}”
puts “The server said: #{reply}”
reply, pointless_number = recv_from_server
send_to_server(‘hello’, first_name, last_name)
def hello(first_name, last_name)
end
define_message_handler(:hello, :handle_hello)
super()
def initialize
class MyServer < PhusionPassenger::AbstractServer
Here’s an example on using AbstractServer:
longer needed, stop() should be called.
Before an AbstractServer can be used, it must first be started by calling start(). When it is no
additional signal handlers using define_signal_handler().
all signals in the default manner. The only exception is SIGHUP, which is ignored. One may define
The server will also reset all signal handlers (in the child process). That is, it will respond to
A message is just an ordered list of strings. The first element in the message is the _message name_.
- The server can pass file descriptors (IO objects) back to the client.
- One can communicate with the server through discrete messages (as opposed to byte streams).
- The server’s main loop may be run in a child process (and so is asynchronous from the main process).
quit when the connection closes.
- The server has exactly one client, and is connected to that client at all times. The server will
An abstract base class for a server, with the following properties:
def before_fork
A hook which is called when the server is being started, just before forking a new process.
def before_fork end
def client
Return the communication channel with the client (i.e. the parent process
def client return @child_channel end
def define_message_handler(message_name, handler)
A message is just a list of strings, and so _handler_ will be called with the message as its
and _handler_ is the name of a method to be called (this may either be a String or a Symbol).
Define a handler for a message. _message_name_ is the name of the message to handle,
def define_message_handler(message_name, handler) @message_handlers[message_name.to_s] = handler end
def define_signal_handler(signal, handler)
def define_signal_handler(signal, handler) @signal_handlers[signal.to_s] = handler end
def finalize_server
after the main loop has been left.
A hook which is called when the server is being stopped. This is called in the child process,
def finalize_server end
def initialize
def initialize @done = false @message_handlers = {} @signal_handlers = {} @orig_signal_handlers = {} @last_activity_time = Time.now end
def initialize_server
before the main loop is entered.
A hook which is called when the server is being started. This is called in the child process,
def initialize_server end
def main_loop
Use define_message_handler() to register a message handler.
and letting registered message handlers handle each message.
The main loop's main function is to read messages from the socket,
The server's main loop. This is called in the child process.
def main_loop channel = MessageChannel.new(@child_socket) while !@done begin name, *args = channel.read @last_activity_time = Time.now if name.nil? @done = true elsif @message_handlers.has_key?(name) __send__(@message_handlers[name], *args) else raise UnknownMessage, "Unknown message '#{name}' received." end rescue Interrupt @done = true rescue SignalException => signal if signal.message == SERVER_TERMINATION_SIGNAL @done = true else raise end end end end
def quit_main
def quit_main @done = true end
def reset_signal_handlers
Reset all signal handlers to default. This is called in the child process,
def reset_signal_handlers Signal.list_trappable.each_key do |signal| begin @orig_signal_handlers[signal] = trap(signal, 'DEFAULT') rescue ArgumentError # Signal cannot be trapped; ignore it. end end @signal_handlers.each_pair do |signal, handler| trap(signal) do __send__(handler) end end trap('HUP', 'IGNORE') end
def revert_signal_handlers
def revert_signal_handlers @orig_signal_handlers.each_pair do |signal, handler| trap(signal, handler) end @orig_signal_handlers.clear end
def server
This method may only be called in the parent process, and not
Raises ServerNotStarted if the server hasn't been started yet.
object.
Return the communication channel with the server. This is a MessageChannel
def server if !started? raise ServerNotStarted, "Server hasn't been started yet. Please call start() first." end return @parent_channel end
def server_pid
def server_pid return @pid end
def start
Otherwise, a ServerAlreadyStarted will be raised.
You may only call this method if the server is not already started.
asynchronously from the current process.
Start the server. This method does not block since the server runs
def start if started? raise ServerAlreadyStarted, "Server is already started" end @parent_socket, @child_socket = UNIXSocket.pair before_fork @pid = fork if @pid.nil? begin STDOUT.sync = true STDERR.sync = true @parent_socket.close # During Passenger's early days, we used to close file descriptors based # on a white list of file descriptors. That proved to be way too fragile: # too many file descriptors are being left open even though they shouldn't # be. So now we close file descriptors based on a black list. file_descriptors_to_leave_open = [0, 1, 2, @child_socket.fileno] NativeSupport.close_all_file_descriptors(file_descriptors_to_leave_open) # In addition to closing the file descriptors, one must also close # the associated IO objects. This is to prevent IO.close from # double-closing already closed file descriptors. close_all_io_objects_for_fds(file_descriptors_to_leave_open) # At this point, RubyGems might have open file handles for which # the associated file descriptors have just been closed. This can # result in mysterious 'EBADFD' errors. So we force RubyGems to # clear all open file handles. Gem.clear_paths start_synchronously(@child_socket) rescue Interrupt # Do nothing. rescue SignalException => signal if signal.message == SERVER_TERMINATION_SIGNAL # Do nothing. else print_exception(self.class.to_s, signal) end rescue Exception => e print_exception(self.class.to_s, e) ensure exit! end end @child_socket.close @parent_channel = MessageChannel.new(@parent_socket) end
def start_synchronously(socket)
loop will end if the socket has been closed.
_socket_ is the socket that the server should listen on. The server main
This method blocks until the server's main loop has ended.
Start the server, but in the current process instead of in a child process.
def start_synchronously(socket) @child_socket = socket @child_channel = MessageChannel.new(socket) begin reset_signal_handlers initialize_server begin main_loop ensure finalize_server end ensure revert_signal_handlers end end
def started?
def started? return !@parent_channel.nil? end
def stop
When calling this method, the server must already be started. If not, a
until the server has been stopped.
Stop the server. The server will quit as soon as possible. This method waits
def stop if !started? raise ServerNotStarted, "Server is not started" end @parent_socket.close @parent_channel = nil # Wait at most 3 seconds for server to exit. If it doesn't do that, # we kill it. If that doesn't work either, we kill it forcefully with # SIGKILL. begin Timeout::timeout(3) do Process.waitpid(@pid) rescue nil end rescue Timeout::Error Process.kill(SERVER_TERMINATION_SIGNAL, @pid) rescue nil begin Timeout::timeout(3) do Process.waitpid(@pid) rescue nil end rescue Timeout::Error Process.kill('SIGKILL', @pid) rescue nil Process.waitpid(@pid, Process::WNOHANG) rescue nil end end end