Getting Started

This guide explains how to use protocol-grpc for building abstract gRPC interfaces.

Installation

Add the gem to your project:

$ bundle add protocol-grpc

Core Concepts

protocol-grpc has several core concepts:

Integration

This gem provides protocol-level abstractions only. To actually send requests over the network, you need an HTTP/2 client/server implementation:

  • Async::GRPC which provides asynchronous client and server implementations.
  • Async::HTTP which provides HTTP/2 transport with connection pooling and concurrency.

Usage

Defining an Interface

class Protocol::GRPC::Interface defines the contract for gRPC services. RPC method names use PascalCase to match .proto files:

require "protocol/grpc/interface"

class GreeterInterface < Protocol::GRPC::Interface
	# Unary RPC (single request, single response)
	rpc :SayHello, Hello::HelloRequest, Hello::HelloReply
	
	# Server streaming RPC using stream() decorator
	rpc :SayHelloMany, Hello::HelloRequest, stream(Hello::HelloReply)
	
	# Client streaming RPC
	rpc :SayHelloRepeatedly, stream(Hello::HelloRequest), Hello::HelloReply
	
	# Bidirectional streaming RPC
	rpc :ChatHello, stream(Hello::HelloRequest), stream(Hello::HelloReply)
end

The stream() decorator marks message types as streamed. You can also use the keyword syntax:

rpc :SayHelloAgain, request_class: Hello::HelloRequest, response_class: Hello::HelloReply,
	streaming: :server_streaming

Building a Request

Build gRPC requests using Protocol::GRPC::Metadata, Protocol::GRPC::Route, and Protocol::GRPC::Body::Writable:

require "protocol/grpc"
require "protocol/grpc/body/writable"

# Build request body
body = Protocol::GRPC::Body::Writable.new(message_class: Hello::HelloRequest)
body.write(Hello::HelloRequest.new(name: "World"))
body.close_write

# Build headers
headers = Protocol::GRPC::Metadata.build(timeout: 5.0)
path = Protocol::GRPC::Route.build("hello.Greeter", "SayHello")

# Create HTTP request
request = Protocol::HTTP::Request["POST", path, headers, body]

Reading a Response

Read gRPC responses using Protocol::GRPC::Body::Readable:

require "protocol/grpc/body/readable"

# Read response body
readable_body = Protocol::GRPC::Body::Readable.new(
	response.body,
	message_class: Hello::HelloReply
)

message = readable_body.read
readable_body.close

# Check gRPC status
status = Protocol::GRPC::Metadata.extract_status(response.headers)
if status != Protocol::GRPC::Status::OK
	message = Protocol::GRPC::Metadata.extract_message(response.headers)
	raise Protocol::GRPC::Error.for(status, message)
end

# Use canonical gRPC status names for logging, metrics, or serialization:
Protocol::GRPC::Status::NAMES[status] # => "OK"

Server Middleware

Create a server middleware by subclassing Protocol::GRPC::Middleware:

require "protocol/grpc/middleware"

class MyMiddleware < Protocol::GRPC::Middleware
	protected
	
	def dispatch(request)
		# Parse the service and method from the path:
		service_name, method_name = Protocol::GRPC::Route.parse(request.path)
		
		# Handle the request using service_name and method_name.
		# ...
	end
end

Call Context

class Protocol::GRPC::Call provides context for a gRPC call:

require "protocol/grpc/call"

call = Protocol::GRPC::Call.new(request, response, deadline: deadline)

# Access request
call.request  # => Protocol::HTTP::Request

# Check deadline and status
call.deadline.exceeded?  # => false
call.status  # => Protocol::GRPC::Status::OK

# Access peer information
call.peer  # => Protocol::HTTP::Address