module Protobuf::Rpc::Server

def handle_client

Invoke the service method dictated by the proto wrapper request object
def handle_client
  @stats.request_size = @buffer.size
  
  # Setup the initial request and response
  @request = Protobuf::Socketrpc::Request.new
  @response = Protobuf::Socketrpc::Response.new
  
  # Parse the protobuf request from the socket
  log_debug "[#{log_signature}] Parsing request from client"
  parse_request_from_buffer

  # Determine the service class and method name from the request
  log_debug "[#{log_signature}] Extracting procedure call info from request"
  parse_service_info
  
  # Call the service method
  log_debug "[#{log_signature}] Dispatching client request to service"
  invoke_rpc_method
rescue => error
  # Ensure we're handling any errors that try to slip out the back door
  log_error error.message
  log_error error.backtrace.join("\n")
  handle_error(error)
  send_response
end

def handle_error(error)

Client error handler. Receives an exception object and writes it into the @response
def handle_error(error)
  log_debug "[#{log_signature}] handle_error: %s" % error.inspect
  if error.respond_to?(:to_response)
    error.to_response(@response)
  else
    message = error.respond_to?(:message) ? error.message : error.to_s
    code = error.respond_to?(:code) ? error.code.to_s : "RPC_ERROR"
    PbError.new(message, code).to_response(@response)
  end
end

def invoke_rpc_method

Assuming all things check out, we can call the service method
def invoke_rpc_method
  # Get a new instance of the service
  @service = @klass.new
  
  # Define our response callback to perform the "successful" response to our client
  # This decouples the service's rpc method from our response to the client,
  # allowing the service to be the dictator for when the response should be sent back.
  #
  # In other words, we don't send the response once the service method finishes executing
  # since the service may perform it's own operations asynchronously.
  @service.on_send_response do |response|
    unless @did_respond
      parse_response_from_service(response)
      send_response
    end
  end
  
  @service.on_rpc_failed do |error|
    unless @did_respond
      handle_error(error)
      send_response
    end
  end
  
  # Call the service method
  log_debug "[#{log_signature}] Invoking %s#%s with request %s" % [@klass.name, @method, @request.inspect]
  @service.__send__(@method, @request)
end

def log_signature

def log_signature
  @log_signature ||= "server-#{self.class}"
end

def parse_request_from_buffer

Parse the incoming request object into our expected request object
def parse_request_from_buffer
  log_debug "[#{log_signature}] parsing request from buffer: %s" % @buffer.data.inspect
  @request.parse_from_string(@buffer.data)
rescue => error
  exc = BadRequestData.new 'Unable to parse request: %s' % error.message
  log_error exc.message
  raise exc
end

def parse_response_from_service(response)

response to the protobuf response wrapper
setting it on the pb request, and serializing the
Read out the response from the service method,
def parse_response_from_service(response)
  expected = @klass.rpcs[@klass][@method].response_type
  
  # Cannibalize the response if it's a Hash
  response = expected.new(response) if response.is_a?(Hash)
  actual = response.class
  log_debug "[#{log_signature}] response (should/actual): %s/%s" % [expected.name, actual.name]
  
  # Determine if the service tried to change response types on us
  if expected == actual
    serialize_response(response)
  else
    # response types do not match, throw the appropriate error
    raise BadResponseProto, 'Response proto changed from %s to %s' % [expected.name, actual.name]
  end
rescue => error
  log_error error.message
  log_error error.backtrace.join("\n")
  handle_error(error)
end

def parse_service_info

Parses and returns the service and method name from the request wrapper proto
def parse_service_info
  @klass = Util.constantize(@request.service_name)
  @method = Util.underscore(@request.method_name).to_sym
  unless @klass.instance_methods.include?(@method)
    raise MethodNotFound, "Service method #{@request.method_name} is not defined by the service"
  end
  
  @stats.service = @klass.name
  @stats.method = @method
rescue NameError
  raise ServiceNotFound, "Service class #{@request.service_name} is not found"
end

def send_response

Write the response wrapper to the client
def send_response
  raise 'Response already sent to client' if @did_respond
  log_debug "[#{log_signature}] Sending response to client: %s" % @response.inspect
  response_buffer = Protobuf::Rpc::Buffer.new(:write, @response)
  send_data(response_buffer.write)
  @stats.response_size = response_buffer.size
  @stats.end
  @stats.log_stats
  @did_respond = true
end

def serialize_response(response)

def serialize_response(response)
  log_debug "[#{log_signature}] serializing response: %s" % response.inspect
  @response.response_proto = response.serialize_to_string
rescue
  raise BadResponseProto, $!.message
end