Class: SimpleRPC::Server
- Inherits:
-
Object
- Object
- SimpleRPC::Server
- Defined in:
- lib/simplerpc/server.rb
Overview
SimpleRPC’s server. This wraps an object and exposes its methods to the network.
i.e.:
require 'simplerpc/server'
# Expose the Array api on port 27045
s = SimpleRPC::Server.new( ["thing", "thing2"], :port => 27045 )
# Listen in a thread so we can shut down later
Thread.new(){ s.listen }
sleep(10)
# Tell the server to exit cleanly
s.close
Thread Safety
The server is thread-safe, and will not interrupt any clients when #close is called (instead it will wait for requests to finish, then shut down).
If :threaded is set to true, the server will be able to make many simultaneous calls to the object being proxied.
Controlling a Server
The server is thread-safe, and is designed to be run in a thread when blocking on #listen — calling #close on a listening server will cause the following chain of events:
-
The current client requests will end
-
The socket will close
-
#listen and #close will return (almost) simultaneously
Serialisation Formats
By default both client and server use Marshal. This has proven fast and general, and is capable of sending data directly over sockets.
The serialiser also supports MessagePack (the msgpack gem), and this yields a small performance increase at the expense of generality (restrictions on data type).
Note that JSON and YAML, though they support reading and writing to sockets, do not properly terminate their reads and cause the system to hang. These methods are both slow and limited by comparison anyway, and algorithms needed to support their use require relatively large memory usage. They may be supported in later versions.
Authentication
Setting the :password and :secret options will require authentication to connect.
Clients and servers do not tell one another to use auth (such a system would impact speed) so the results of using mismatched configurations are undefined.
The auth process is simple and not particularly secure, but is designed to deter casual connections and attacks. It uses a password that is sent encrypted against a salt sent by the server to prevent replay attacks. If you want more reliable security, use an SSH tunnel.
The performance impact of auth is small, and takes about the same time as a simple request. This can be mitigated by using always-on mode.
Instance Attribute Summary collapse
-
#fast_auth ⇒ Object
Returns the value of attribute fast_auth.
-
#hostname ⇒ Object
readonly
Returns the value of attribute hostname.
-
#obj ⇒ Object
readonly
Returns the value of attribute obj.
-
#password ⇒ Object
writeonly
Sets the attribute password.
-
#port ⇒ Object
readonly
Returns the value of attribute port.
-
#secret ⇒ Object
writeonly
Sets the attribute secret.
-
#serialiser ⇒ Object
Returns the value of attribute serialiser.
-
#threaded ⇒ Object
readonly
Returns the value of attribute threaded.
-
#timeout ⇒ Object
Returns the value of attribute timeout.
-
#verbose_errors ⇒ Object
Returns the value of attribute verbose_errors.
Instance Method Summary collapse
-
#active_client_threads ⇒ Object
Return the number of active client threads.
-
#close(timeout = false) ⇒ Object
Close the server object nicely, waiting on threads if necessary.
-
#initialize(obj, opts = {}) ⇒ Server
constructor
Create a new server for a given proxy object.
-
#listen ⇒ Object
Start listening forever.
Constructor Details
#initialize(obj, opts = {}) ⇒ Server
Create a new server for a given proxy object.
The single required parameter, obj, is an object you wish to expose to the network. This is the API that will respond to RPCs.
Takes an option hash with options:
- :port
-
The port on which to listen, or 0 for the OS to set it
- :hostname
-
The hostname of the interface to listen on (omit for all interfaces)
- :serialiser
-
A class supporting #load(IO) and #dump(obj, IO) for serialisation. Defaults to Marshal. I recommend using MessagePack if this is not fast enough. Note that JSON/YAML do not work as they don’t send terminating characters over the socket.
- :verbose_errors
-
Report all socket errors from clients (by default these will be quashed).
- :timeout
-
Socket timeout in seconds. Default is infinite (nil)
- :threaded
-
Accept more than one client at once? Note that proxy object should be thread-safe for this Default is on.
- :password
-
The password clients need to connect
- :secret
-
The encryption key used during password authentication. Should be some long random string. This should be ASCII-8bit encoded (it will be converted if not)
- :salt_size
-
The size of the string used as a nonce during password auth. Defaults to 10 chars
- :fast_auth
-
Use a slightly faster auth system that is incapable of knowing if it has failed or not. By default this is off.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/simplerpc/server.rb', line 108 def initialize(obj, opts = {}) @obj = obj @port = opts[:port].to_i @hostname = opts[:hostname] # What format to use. @serialiser = opts[:serialiser] || Marshal # Silence errors coming from client connections? @verbose_errors = (opts[:verbose_errors] == true) @fast_auth = (opts[:fast_auth] == true) # Should we shut down? @close = false @close_in, @close_out = UNIXSocket.pair # Connect/receive timeouts timeout = opts[:timeout] # Auth if opts[:password] && opts[:secret] require 'securerandom' require 'simplerpc/encryption' @password = opts[:password] @secret = opts[:secret] @salt_size = opts[:salt_size] || 10 # size of salt on key. end # Threaded or not? @threaded = !(opts[:threaded] == false) if @threaded @clients = {} @mc = Mutex.new # Client list mutex end # Listener mutex @ml = Mutex.new end |
Instance Attribute Details
#fast_auth ⇒ Object
Returns the value of attribute fast_auth.
81 82 83 |
# File 'lib/simplerpc/server.rb', line 81 def fast_auth @fast_auth end |
#hostname ⇒ Object (readonly)
Returns the value of attribute hostname.
80 81 82 |
# File 'lib/simplerpc/server.rb', line 80 def hostname @hostname end |
#obj ⇒ Object (readonly)
Returns the value of attribute obj.
80 81 82 |
# File 'lib/simplerpc/server.rb', line 80 def obj @obj end |
#password=(value) ⇒ Object (writeonly)
Sets the attribute password
82 83 84 |
# File 'lib/simplerpc/server.rb', line 82 def password=(value) @password = value end |
#port ⇒ Object (readonly)
Returns the value of attribute port.
80 81 82 |
# File 'lib/simplerpc/server.rb', line 80 def port @port end |
#secret=(value) ⇒ Object (writeonly)
Sets the attribute secret
82 83 84 |
# File 'lib/simplerpc/server.rb', line 82 def secret=(value) @secret = value end |
#serialiser ⇒ Object
Returns the value of attribute serialiser.
81 82 83 |
# File 'lib/simplerpc/server.rb', line 81 def serialiser @serialiser end |
#threaded ⇒ Object (readonly)
Returns the value of attribute threaded.
80 81 82 |
# File 'lib/simplerpc/server.rb', line 80 def threaded @threaded end |
#timeout ⇒ Object
Returns the value of attribute timeout.
80 81 82 |
# File 'lib/simplerpc/server.rb', line 80 def timeout @timeout end |
#verbose_errors ⇒ Object
Returns the value of attribute verbose_errors.
81 82 83 |
# File 'lib/simplerpc/server.rb', line 81 def verbose_errors @verbose_errors end |
Instance Method Details
#active_client_threads ⇒ Object
Return the number of active client threads.
Returns 0 if :threaded is set to false.
229 230 231 232 233 234 235 |
# File 'lib/simplerpc/server.rb', line 229 def active_client_threads # If threaded return a count from the clients list return @clients.length if @threaded # Else return 0 if not threaded return 0 end |
#close(timeout = false) ⇒ Object
Close the server object nicely, waiting on threads if necessary
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/simplerpc/server.rb', line 239 def close(timeout = false) # Return immediately if the server isn't listening return unless @ml.locked? # Ask the loop to close @close_in.putc 'x' # Tell select to close # Wait for loop to end elapsed_time = 0 while @ml.locked? do sleep(0.05) elapsed_time += 0.05 # If a timeout is given, try killing threads at this point if timeout && elapsed_time > timeout @clients.each {|id, thread| thread.kill() } end end end |
#listen ⇒ Object
Start listening forever.
Use threads and .close to stop the server.
Throws AlreadyListeningError when the server is already busy listening for connections
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/simplerpc/server.rb', line 168 def listen raise 'Server is already listening' unless @ml.try_lock # Listen on one interface only if hostname given s = create_server_socket # Handle clients loop do # Accept in an interruptable manner if (c = interruptable_accept(s)) # Set timeout directly on socket if @socket_timeout c.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, @socket_timeout) c.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, @socket_timeout) end # Threaded if @threaded # Create the id id = rand.hash # Add to the client list @mc.synchronize do @clients[id] = Thread.new() do # puts "[#{@clients.length+1}->#{id}" begin handle_client(c) ensure # Remove self from list @mc.synchronize { @clients.delete(id) } # puts "[#{@clients.length}<-#{id}" end end @clients[id].abort_on_exception = true end # Single-threaded else # Handle client handle_client(c) end end break if @close end # Wait for threads to end @clients.each {|id, thread| thread.join } if @threaded # Close socket @close = false if @close # say we've closed ensure @ml.unlock end |