class THTP::Server
An HTTP (Rack middleware) implementation of Thrift-RPC
Attributes
service[R]
Public Class Methods
new(app = NullRoute.new, service:, handlers: [])
click to toggle source
@param app [Object?] The Rack application underneath, if used as middleware @param service [Thrift::Service] The service class handled by this server @param handlers [Object,Array<Object>] The object(s) handling RPC requests
# File lib/thtp/server.rb, line 26 def initialize(app = NullRoute.new, service:, handlers: []) @app = app @service = service @handler = MiddlewareStack.new(service, handlers) @route = %r{^/#{canonical_name(service)}/(?<rpc>[\w.]+)/?$} # /:service/:rpc end
Public Instance Methods
call(rack_env)
click to toggle source
Rack implementation entrypoint
# File lib/thtp/server.rb, line 39 def call(rack_env) start_time = get_time # verify routing request = Rack::Request.new(rack_env) protocol = Encoding.protocol(request.media_type) || Thrift::JsonProtocol return @app.call(rack_env) unless request.post? && @route.match(request.path_info) # get RPC name from route rpc = Regexp.last_match[:rpc] raise UnknownRpcError, rpc unless @handler.respond_to?(rpc) # read, perform, write args = read_args(request.body, rpc, protocol) result = @handler.public_send(rpc, *args) write_reply(result, rpc, protocol).tap do publish :rpc_success, request: request, rpc: rpc, args: args, result: result, time: elapsed_ms(start_time) end rescue Thrift::Exception => e # known schema-defined Thrift errors write_reply(e, rpc, protocol).tap do publish :rpc_exception, request: request, rpc: rpc, args: args, exception: e, time: elapsed_ms(start_time) end rescue ServerError => e # known server/communication errors write_error(e, protocol).tap do publish :rpc_error, request: request, rpc: rpc, args: args, error: e, time: elapsed_ms(start_time) end rescue => e # a non-Thrift exception occurred; translate to Thrift as best we can write_error(InternalError.new(e), protocol).tap do publish :internal_error, request: request, error: e, time: elapsed_ms(start_time) end end
use(middleware_class, *middleware_args)
click to toggle source
delegate to RPC handler stack
# File lib/thtp/server.rb, line 34 def use(middleware_class, *middleware_args) @handler.use(middleware_class, *middleware_args) end
Private Instance Methods
read_args(request_body, rpc, protocol)
click to toggle source
fetches args from a request
# File lib/thtp/server.rb, line 74 def read_args(request_body, rpc, protocol) args_struct = args_class(service, rpc).new # read off the request body into a Thrift args struct deserialize_stream(request_body, args_struct, protocol) # args are named methods, but handler signatures use positional arguments; # convert between the two using struct_fields, which is an ordered hash. args_struct.struct_fields.values.map { |f| args_struct.public_send(f[:name]) } end
write_error(exception, protocol)
click to toggle source
Given an unexpected error (non-schema), try to write it schemaless. The status code indicate to clients that an error occurred and should be deserialised. The implicit schema for a non-schema exception is:
struct exception { 1: string message, 2: i32 type }
@param exception [Errors::ServerError]
# File lib/thtp/server.rb, line 113 def write_error(exception, protocol) # write to the response as an EXCEPTION message [ Status::EXCEPTION, { Rack::CONTENT_TYPE => Encoding.content_type(protocol) }, [serialize_buffer(exception.to_thrift, protocol)], ] end
write_reply(reply, rpc, protocol)
click to toggle source
given any schema-defined response (success or exception), write it to the HTTP response
# File lib/thtp/server.rb, line 84 def write_reply(reply, rpc, protocol) result_struct = result_class(service, rpc).new # void return types have no spot in the result struct unless reply.nil? if reply.is_a?(Thrift::Exception) # detect the correct exception field, if it exists, and set its value field = result_struct.struct_fields.values.find do |f| f.key?(:class) && reply.instance_of?(f[:class]) end raise BadResponseError, rpc, reply unless field result_struct.public_send("#{field[:name]}=", reply) else # if it's not an exception, it must be the "success" value result_struct.success = reply end end # write to the response as a REPLY message [ Status::REPLY, { Rack::CONTENT_TYPE => Encoding.content_type(protocol) }, [serialize_buffer(result_struct, protocol)], ] end