Class: Barrister::Server

Inherits:
Object
  • Object
show all
Includes:
Barrister
Defined in:
lib/barrister.rb

Overview

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.

Instance Method Summary collapse

Methods included from Barrister

contract_from_file, #err_resp, #ok_resp, parse_method, rand_str

Constructor Details

#initialize(contract) ⇒ Server

Create a server with the given Barrister::Contract instance



144
145
146
147
# File 'lib/barrister.rb', line 144

def initialize(contract)
  @contract = contract
  @handlers = { }
end

Instance Method Details

#add_handler(iface_name, handler) ⇒ Object

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.



155
156
157
158
159
160
161
# 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) ⇒ Object

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.



183
184
185
186
187
188
189
190
191
192
193
# 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) ⇒ Object

Handles a request encoded as JSON.

Returns the result as a JSON encoded string.



165
166
167
168
169
170
171
172
173
174
175
# 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) ⇒ Object

Internal method that validates and executes a single request.



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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# 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