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

new(contract) click to toggle source

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

add_handler(iface_name, handler) click to toggle source

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
handle(req) click to toggle source

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
handle_json(json_str) click to toggle source

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
handle_single(req) click to toggle source

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