Class: By::Server
- Inherits:
-
Object
- Object
- By::Server
- Defined in:
- lib/by/server.rb
Class Method Summary collapse
-
.with_argument_handler(&block) ⇒ Object
Return a subclass that will use a Worker subclass with a handle_args method defined by the given block.
Instance Method Summary collapse
-
#accept_client ⇒ Object
Accept a new client connection, or return nil if stop_accepting_clients! has been called.
-
#accept_clients ⇒ Object
Accept each client connection and fork a worker socket for it.
-
#auto_require_files ⇒ Object
Files to automatically require, uses the
BY_SERVER_AUTO_REQUIRE
environment variable by default. -
#daemonize ⇒ Object
Daemonize with configured daemon args using Process.daemon.
-
#daemonize? ⇒ Boolean
Whether to daemonize.
-
#default_argv ⇒ Object
The default server arguments, uses
ARGV
by default. -
#default_daemon_args ⇒ Object
The default arguments when daemonizing.
-
#default_daemonize ⇒ Object
The default for whether to daemonize.
-
#default_debug ⇒ Object
The default debug mode.
-
#default_socket_path ⇒ Object
The default socket path to use.
-
#default_worker_class ⇒ Object
The default worker class to use for worker processes, Worker by default.
-
#fork_worker(socket) ⇒ Object
Fork a worker process to handle the client connection.
-
#handle_argv ⇒ Object
Handle arguments provided to the server.
-
#handle_existing_server ⇒ Object
Handle an existing server socket.
-
#initialize(socket_path: default_socket_path, argv: default_argv, debug: default_debug, daemonize: default_daemonize, daemon_args: default_daemon_args, worker_class: default_worker_class) ⇒ Server
constructor
Creates a new server.
-
#print_loaded_features ⇒ Object
Print
$LOADED_FEATURES
to stdout. -
#run ⇒ Object
Runs the server.
-
#setup_server ⇒ Object
Creates and listens on the server socket.
-
#setup_signals ⇒ Object
Trap SIGTERM and have it stop accepting clients.
-
#stop? ⇒ Boolean
Whether to only stop an existing server and not start a new server.
-
#stop_accepting_clients! ⇒ Object
Close the server socket.
Constructor Details
#initialize(socket_path: default_socket_path, argv: default_argv, debug: default_debug, daemonize: default_daemonize, daemon_args: default_daemon_args, worker_class: default_worker_class) ⇒ Server
Creates a new server. Arguments: socket_path: The path to the UNIX socket to create and listen on. argv: The arguments to the server, which are libraries to be required by default. debug: If set, operations on an existing server socket will be logged.
If the value is <tt>'log'</tt>, <tt>$LOADED_FEATURES</tt> will also be logged to the stdout
after libraries have been required.
daemonize: Whether to daemonize, true
by default. daemon_args: Arguments to use when daemonizing, [false, false]
by default. worker_class: The class to use for worker process handling, Worker by default.
30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/by/server.rb', line 30 def initialize(socket_path: default_socket_path, argv: default_argv, debug: default_debug, daemonize: default_daemonize, daemon_args: default_daemon_args, worker_class: default_worker_class) @socket_path = socket_path @argv = argv @debug = debug if @daemonize = !!daemonize @daemon_args = Array(daemon_args) end @worker_class = worker_class end |
Class Method Details
.with_argument_handler(&block) ⇒ Object
Return a subclass that will use a Worker subclass with a handle_args method defined by the given block. Allows for easily customizing/overriding the default argument handling.
12 13 14 15 16 17 18 19 |
# File 'lib/by/server.rb', line 12 def self.with_argument_handler(&block) worker_subclass = Class.new(new.default_worker_class) do define_method(:handle_args, &block) end Class.new(self) do define_method(:default_worker_class){worker_subclass} end end |
Instance Method Details
#accept_client ⇒ Object
Accept a new client connection, or return nil if stop_accepting_clients! has been called.
181 182 183 184 185 186 |
# File 'lib/by/server.rb', line 181 def accept_client @socket.accept rescue IOError, Errno::EBADF # likely closed stream, return nil to exit accept_clients loop nil end |
#accept_clients ⇒ Object
Accept each client connection and fork a worker socket for it. Terminate loop when stop_accepting_clients! is called.
173 174 175 176 177 |
# File 'lib/by/server.rb', line 173 def accept_clients while socket = accept_client fork_worker(socket) end end |
#auto_require_files ⇒ Object
Files to automatically require, uses the BY_SERVER_AUTO_REQUIRE
environment variable by default.
132 133 134 |
# File 'lib/by/server.rb', line 132 def auto_require_files (ENV['BY_SERVER_AUTO_REQUIRE'] || '').split end |
#daemonize ⇒ Object
Daemonize with configured daemon args using Process.daemon.
146 147 148 |
# File 'lib/by/server.rb', line 146 def daemonize Process.daemon(*@daemon_args) end |
#daemonize? ⇒ Boolean
Whether to daemonize.
151 152 153 |
# File 'lib/by/server.rb', line 151 def daemonize? !!@daemonize end |
#default_argv ⇒ Object
The default server arguments, uses ARGV
by default.
49 50 51 |
# File 'lib/by/server.rb', line 49 def default_argv ARGV end |
#default_daemon_args ⇒ Object
The default arguments when daemonizing. By default, considers the BY_SERVER_DAEMON_NO_CHDIR
and BY_SERVER_DAEMON_NO_REDIR_STDIO
environment variables.
67 68 69 |
# File 'lib/by/server.rb', line 67 def default_daemon_args [!!ENV['BY_SERVER_DAEMON_NO_CHDIR'], !!ENV['BY_SERVER_DAEMON_NO_REDIR_STDIO']] end |
#default_daemonize ⇒ Object
The default for whether to daemonize. It is true if the BY_SERVER_NO_DAEMON
environment variable is not set.
60 61 62 |
# File 'lib/by/server.rb', line 60 def default_daemonize !ENV['BY_SERVER_NO_DAEMON'] end |
#default_debug ⇒ Object
The default debug mode. This uses and removes the DEBUG
environment variable.
54 55 56 |
# File 'lib/by/server.rb', line 54 def default_debug ENV.delete('DEBUG') end |
#default_socket_path ⇒ Object
The default socket path to use. Use the BY_SOCKET
environment variable if set, or ~/.by_socket
if not set.
44 45 46 |
# File 'lib/by/server.rb', line 44 def default_socket_path ENV['BY_SOCKET'] || File.join(ENV["HOME"], '.by_socket') end |
#default_worker_class ⇒ Object
The default worker class to use for worker processes, Worker by default.
72 73 74 |
# File 'lib/by/server.rb', line 72 def default_worker_class Worker end |
#fork_worker(socket) ⇒ Object
Fork a worker process to handle the client connection. Close the given socket after the fork, so the socket will open be open in the worker process.
197 198 199 200 201 202 203 204 |
# File 'lib/by/server.rb', line 197 def fork_worker(socket) Process.detach(Process.fork do Signal.trap(:QUIT, @sigquit_default) Signal.trap(:TERM, @sigterm_default) @worker_class.new(socket).run end) socket.close end |
#handle_argv ⇒ Object
Handle arguments provided to the server. Requires each argument by default.
125 126 127 128 |
# File 'lib/by/server.rb', line 125 def handle_argv (auto_require_files + @argv).each{|f| require f} print_loaded_features if @debug == 'log' end |
#handle_existing_server ⇒ Object
Handle an existing server socket. This attempts to connect to the socket and then shutdown the server. If successful, it removes the socket. If unsuccessful, it will print an error.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/by/server.rb', line 94 def handle_existing_server if File.socket?(@socket_path) begin @socket = UNIXSocket.new(@socket_path) print "Shutting down existing by_server at #{@socket_path}..." if @debug raise "Invalid by_server worker pid" unless @socket.readline("\0", chomp: true).to_i > 1 @socket.send_io($stdin) @socket.send_io($stdout) @socket.send_io($stderr) @socket.write("stop") @socket.shutdown(Socket::SHUT_WR) @socket.read @socket.close rescue => e puts "FAILED!!!" if @debug $stderr.puts "Error shutting down server on existing socket: #{e.class}: #{e.}" exit(1) else puts "Success!" if @debug end @socket = nil File.delete(@socket_path) end end |
#print_loaded_features ⇒ Object
Print $LOADED_FEATURES
to stdout.
207 208 209 |
# File 'lib/by/server.rb', line 207 def print_loaded_features puts $LOADED_FEATURES end |
#run ⇒ Object
Runs the server. This will not terminate until the server receives SIGTERM or stop_accepting_clients! is called manually.
If stop? is true, does not run a server, just handles an existing server.
80 81 82 83 84 85 86 87 88 89 |
# File 'lib/by/server.rb', line 80 def run handle_existing_server return if stop? handle_argv setup_server daemonize if daemonize? setup_signals accept_clients end |
#setup_server ⇒ Object
Creates and listens on the server socket.
137 138 139 140 141 142 143 |
# File 'lib/by/server.rb', line 137 def setup_server # Prevent TOCTOU on server socket creation umask = File.umask(077) @socket = UNIXServer.new(@socket_path) File.umask(umask) system('chmod', '600', @socket_path) end |
#setup_signals ⇒ Object
Trap SIGTERM and have it stop accepting clients. Trap SIGTERM and have it remove the socket and stop accepting clients.
157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/by/server.rb', line 157 def setup_signals @sigquit_default = Signal.trap(:QUIT) do stop_accepting_clients! end @sigterm_default = Signal.trap(:TERM) do begin File.delete(@socket_path) rescue Errno::ENOENT # server socket already deleted, ignore end stop_accepting_clients! end end |
#stop? ⇒ Boolean
Whether to only stop an existing server and not start a new server.
120 121 122 |
# File 'lib/by/server.rb', line 120 def stop? @argv == ['stop'] end |
#stop_accepting_clients! ⇒ Object
Close the server socket. This will trigger the accept_clients loop to terminate.
190 191 192 |
# File 'lib/by/server.rb', line 190 def stop_accepting_clients! @socket.close end |