class PhusionPassenger::AbstractServer
server.stop
server.hello(“Joe”, “Dalton”)
server.start
server = MyServer.new
end
end
channel.write(“Hello #{first_name} #{last_name}, how are you?”, 1234)
def handle_hello(channel, first_name, last_name)
private
end
end
puts “In addition, it sent this pointless number: #{pointless_number}”
puts “The server said: #{reply}”
reply, pointless_number = channel.read
channel.write(‘hello’, first_name, last_name)
connect do |channel|
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:
When it is no longer needed, stop() should be called.
Before an AbstractServer can be used, it must first be started by calling start().
ignored. One may define additional signal handlers using define_signal_handler().
all signals in the default manner. The only exception is SIGHUP, which is
The server will also reset all signal handlers. That is, it will respond to
- The server can pass file descriptors (IO objects) back to the client.
as opposed to byte streams.
- One can communicate with the server through discrete MessageChannel messages,
from the parent process).
- The server’s main loop may be run in a child process (and so is asynchronous
reference to the server, then the server will quit.
- The server is owned by one or more processes. If all processes close their
- The server is multithreaded and handles one client per thread.
- The server listens on a password protected Unix socket.
An abstract base class for a server that has 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 connect
end
...
channel.write("a message")
server.connect do |channel|
will be closed.
be used for a single handler cycle; after the handler is done, the connection
The first message's name must match a handler name. The connection can only
Connects to the server and yields a channel for communication.
def connect channel = MessageChannel.new(UNIXSocket.new(@socket_filename)) begin channel.write_scalar(@password) yield channel ensure channel.close end 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 fileno_of(io)
def fileno_of(io) return io.fileno rescue return nil 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(socket_filename = nil, password = nil)
def initialize(socket_filename = nil, password = nil) @socket_filename = socket_filename @password = password @socket_filename ||= "#{passenger_tmpdir}/spawn-server/socket.#{Process.pid}.#{object_id}" @password ||= generate_random_id(:base64) @message_handlers = {} @signal_handlers = {} @orig_signal_handlers = {} 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 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_main_loop(password, server_socket)
def server_main_loop(password, server_socket) while true ios = select([@owner_socket, server_socket]).first if ios.include?(server_socket) client_socket = server_socket.accept begin client = MessageChannel.new(client_socket) client_password = client.read_scalar if client_password != password next end name, *args = client.read if name if @message_handlers.has_key?(name) __send__(@message_handlers[name], client, *args) else raise UnknownMessage, "Unknown message '#{name}' received." end end ensure client_socket.close end else break end end 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 a, b = UNIXSocket.pair File.unlink(@socket_filename) rescue nil server_socket = UNIXServer.new(@socket_filename) File.chmod(0700, @socket_filename) before_fork @pid = fork if @pid.nil? has_exception = false begin STDOUT.sync = true STDERR.sync = true a.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. # # Note that STDIN, STDOUT and STDERR may be temporarily set to # different file descriptors than 0, 1 and 2, e.g. in unit tests. # We don't want to close these either. file_descriptors_to_leave_open = [0, 1, 2, b.fileno, server_socket.fileno, fileno_of(STDIN), fileno_of(STDOUT), fileno_of(STDERR) ].compact.uniq 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 # Reseed pseudo-random number generator for security reasons. srand start_synchronously(@socket_filename, @password, server_socket, b) rescue Interrupt # Do nothing. has_exception = true rescue Exception => e has_exception = true print_exception(self.class.to_s, e) ensure exit!(has_exception ? 1 : 0) end end server_socket.close b.close @owner_socket = a end
def start_synchronously(socket_filename, password, server_socket, owner_socket)
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_filename, password, server_socket, owner_socket) @owner_socket = owner_socket begin reset_signal_handlers initialize_server begin server_main_loop(password, server_socket) ensure finalize_server end rescue Interrupt # Do nothing ensure @owner_socket = nil revert_signal_handlers File.unlink(socket_filename) rescue nil server_socket.close end end
def started?
def started? return !!@owner_socket 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 begin @owner_socket.write("x") rescue Errno::EPIPE end @owner_socket.close @owner_socket = nil File.unlink(@socket_filename) rescue nil # Wait at most 4 seconds for server to exit. If it doesn't do that, # we kill it forcefully with SIGKILL. if !Process.timed_waitpid(@pid, 4) Process.kill('SIGKILL', @pid) rescue nil Process.timed_waitpid(@pid, 1) end end