class Barrister::Server
The Server
class is responsible for taking an incoming request, validating the method and params, invoking the correct handler function (your code), and returning the result.
Server
has a Barrister::Contract
that is initialized in the contructor. It uses the Contract
for validation.
The Server
doesn’t do any network communication. It contains a default ‘handle_json` convenience method that encapsulates JSON serialization, and a lower level `handle` method. This will make it easy to add other serialization formats (such as MessagePack) later.
Public Class Methods
Create a server with the given Barrister::Contract
instance
# File lib/barrister.rb, line 144 def initialize(contract) @contract = contract @handlers = { } end
Public Instance Methods
Register a handler class with the given interface name
The ‘handler` is any Ruby class that contains methods for each function on the given IDL interface name.
These methods will be called when a request is handled by the Server
.
# File lib/barrister.rb, line 155 def add_handler(iface_name, handler) iface = @contract.interface(iface_name) if !iface raise "No interface found with name: #{iface_name}" end @handlers[iface_name] = handler end
Handles a deserialized request and returns the result
‘req` must either be a Hash (single request), or an Array (batch request)
‘handle` returns an Array of results for batch requests, and a single Hash for single requests.
# File lib/barrister.rb, line 183 def handle(req) if req.kind_of?(Array) resp_list = [ ] req.each do |r| resp_list << handle_single(r) end return resp_list else return handle_single(req) end end
Handles a request encoded as JSON.
Returns the result as a JSON encoded string.
# File lib/barrister.rb, line 165 def handle_json(json_str) begin req = JSON::parse(json_str) resp = handle(req) rescue JSON::ParserError => e resp = err_resp({ }, -32700, "Unable to parse JSON: #{e.message}") end # Note the `:ascii_only` usage here. Important. return JSON::generate(resp, { :ascii_only=>true }) end
Internal method that validates and executes a single request.
# File lib/barrister.rb, line 196 def handle_single(req) method = req["method"] if !method return err_resp(req, -32600, "No method provided on request") end # Special case - client is requesting the IDL bound to this server, so # we return it verbatim. No further validation is needed in this case. if method == "barrister-idl" return ok_resp(req, @contract.idl) end # Make sure we can find an interface and function on the IDL for this # request method string err_resp, iface, func = @contract.resolve_method(req) if err_resp != nil return err_resp end # Make sure that the params on the request match the IDL types err_resp = @contract.validate_params(req, func) if err_resp != nil return err_resp end params = [ ] if req["params"] params = req["params"] end # Make sure we have a handler bound to this Server for the interface. # If not, that means `server.add_handler` was not called for this interface # name. That's likely a misconfiguration. handler = @handlers[iface.name] if !handler return err_resp(req, -32000, "Server error. No handler is bound to interface #{iface.name}") end # Make sure that the handler has a method for the given function. if !handler.respond_to?(func.name) return err_resp(req, -32000, "Server error. Handler for #{iface.name} does not implement #{func.name}") end begin # Call the handler function. This is where your code gets invoked. result = handler.send(func.name, *params) # Verify that the handler function's return value matches the # correct type as specified in the IDL err_resp = @contract.validate_result(req, result, func) if err_resp != nil return err_resp else return ok_resp(req, result) end rescue RpcException => e # If the handler raised a RpcException, that's ok - return it unmodified. return err_resp(req, e.code, e.message, e.data) rescue => e # If any other error was raised, print it and return a generic error to the client puts e.inspect puts e.backtrace return err_resp(req, -32000, "Unknown error: #{e}") end end