class Dispatcher
Dispatches gRPC requests to registered services. Handles routing based on service name from the request path.
server = Async::HTTP::Server.for(endpoint, dispatcher)
Example: Registering services:
dispatcher = Dispatcher.new
dispatcher.register(GreeterService.new(GreeterInterface, "hello.Greeter"))
dispatcher.register(WorldService.new(WorldInterface, "world.Greeter"))
Signature
Definitions
def initialize(app = nil, services: {})
Initialize the dispatcher.
Signature
-
parameter
app#call | Nil The next middleware in the chain
-
parameter
servicesHash Optional initial services hash (service_name => service_instance)
Implementation
def initialize(app = nil, services: {})
super(app)
@services = services
end
def register(service, name: service.service_name)
Register a service.
Signature
-
parameter
serviceAsync::GRPC::Service Service instance
-
parameter
nameString Service name (defaults to service.service_name)
Implementation
def register(service, name: service.service_name)
@services[name] = service
end
def invoke_service(service, handler_method, input, output, call)
Invoke a service handler, providing a protected extension point for middleware and instrumentation around service execution.
Override this method to observe service outcomes, establish request context, or wrap handlers with application middleware. Overrides should call super to invoke the handler and preserve the dispatcher's stream cleanup, trailer handling, and successful status assignment.
This hook only surrounds service handler execution. Errors raised earlier while routing or preparing the request do not pass through it. A call deadline that expires during service execution raises class Async::GRPC::DeadlineExceededError through this hook before the dispatcher converts it to a DEADLINE_EXCEEDED response.
Example: Wrap service execution with application middleware
def invoke_service(service, handler_method, input, output, call)
context = RequestContext.new(call.request)
middleware.call(context) do
super
end
end
Signature
-
parameter
serviceAsync::GRPC::Service The service containing the handler.
-
parameter
handler_methodSymbol The service method to invoke.
-
parameter
inputProtocol::GRPC::Body::ReadableBody The decoded request stream.
-
parameter
outputProtocol::GRPC::Body::WritableBody The response stream.
-
parameter
callProtocol::GRPC::Call The call context.
-
returns
Object | Nil An internal dispatch result with no stable application-facing contract.
Implementation
def invoke_service(service, handler_method, input, output, call)
begin
service.send(handler_method, input, output, call)
ensure
# Close input stream:
input.close
# Close output stream:
output.close_write unless output.closed?
# gRPC supports trailers-only responses, but only if there are no data frames. If at this point, there are data frames (which may or may not have been sent yet), we need to mark trailers:
if output.count > 0
call.response.headers.trailer!
end
end
# Add status (if not already set by handler):
if headers = call.response&.headers
# Only add OK status if grpc-status hasn't been set by the handler:
unless headers.key?("grpc-status")
Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::OK)
end
end
end
def dispatch(request)
Dispatch the request to the appropriate service.
Signature
-
parameter
requestProtocol::HTTP::Request The HTTP request
-
returns
Protocol::HTTP::Response The HTTP response
-
raises
Protocol::GRPC::Error If service or method is not found
Implementation
def dispatch(request)
# Parse service and method from path:
service_name, method_name = Protocol::GRPC::Methods.parse_path(request.path)
# Find service:
service = @services[service_name]
unless service
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service not found: #{service_name}")
end
# Verify service name matches:
unless service_name == service.service_name
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service name mismatch: expected #{service.service_name}, got #{service_name}")
end
# Get RPC descriptions from the service:
rpc_descriptor = service.rpc_descriptions[method_name]
unless rpc_descriptor
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Method not found: #{method_name}")
end
handler_method = rpc_descriptor.method
request_class = rpc_descriptor.request_class
response_class = rpc_descriptor.response_class
# Verify handler method exists:
unless service.respond_to?(handler_method, true)
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Handler method not implemented: #{handler_method}")
end
# Create protocol-level objects for gRPC handling:
encoding = request.headers["grpc-encoding"]
input = Protocol::GRPC::Body::ReadableBody.new(request.body, message_class: request_class, encoding: encoding)
output = Protocol::GRPC::Body::WritableBody.new(message_class: response_class, encoding: encoding)
# Create response headers:
response_headers = Protocol::HTTP::Headers.new([], nil, policy: Protocol::GRPC::HEADER_POLICY)
response_headers["content-type"] = "application/grpc+proto"
response_headers["grpc-encoding"] = encoding if encoding
# Create response object:
response = Protocol::HTTP::Response[200, response_headers, output]
# Create call context with request, response and deadline:
call = Protocol::GRPC::Call.for(request, response)
if rpc_descriptor.streaming?
Async do |task|
dispatch_to_service(service, handler_method, input, output, call, parent: task)
end
else
# Unary call:
dispatch_to_service(service, handler_method, input, output, call)
end
return response
end