the :grpc plugin provides a DSL for declaring GRPC clients and perform GRPC calls via httpx and its HTTP/2 “bus”.

It supports:

  • GRPC unary requests
  • client, server, bidirectional streaming
  • Authentication
  • Compression
  • Cancellations

This is how you’d use the DSL to build grpc clients:

grpc = HTTPX.plugin(:grpc)

helloworld_stub = grpc.build_stub("localhost:4545")
routeguide_stub = grpc.build_stub("localhost:4546")

Unary Requests

# unary RPC
helloworld_svc = helloworld_stub.rpc(:SayHello, HelloRequest, HelloReply)

result = helloworld_svc.say_hello( "Jack")) #=> HelloReply: "Hello Jack"
# result is a call. You can check metadata, like:
# result.metadata #=> headers list
# result.trailing_metadata #=> trailers list
result.value #=> "Hello Jack"

Server Streaming

# server, client and bidi stream RPCs respectively
routeguide_svc = routeguide_stub.rpc(:RouteGuide, ListFeatures, Feature, stream: true)

result = routeguide_svc.list_features(
    lo: 400_000_000, longitude: -750_000_000),
    hi: 420_000_000, longitude: -730_000_000)

# at this point, you can ask for metadata, but not trailing metadata, until you consume the response
result.each do |feature|
  puts "- found '#{}' at #{r.location.inspect}"
# and now you can ask for the trailing metadata

Client Streaming

Client streaming is implicit, provided you pass an enumerable which yields instances of the supported protobuf:

routeguide_svc = routeguide_stub.rpc(:RecordRoute, Point, RouteSummary)

sleeping_enumerator =, 1)

reqs =, points_on_route)
resp = routeguide_svc.record_route(reqs.method(:each))
puts "summary: #{resp.inspect}"

Bidirectional Streaming

Bidirectional streaming is achieved by combined both previous approaches, i.e. by passing an enumerator, and receiving a streamed enumerator.

routeguide_svc = routeguide_stub.rpc(:RouteChat, RouteNote, RouteNote, stream: true)

# enumerator which yields and sleeps one second recursively
sleeping_enumerator =, 1)

result = routeguide_svc.route_chat(sleeping_enumerator.method(:each_item))

result.each { |r| p "received #{r.inspect}" }


The grpc plugin already takes care of decompressing grpc responses. For requests however, you’ll have to explicitly enable it:

# unary RPC
helloworld_svc = helloworld_stub.rpc(:SayHello, HelloRequest, HelloReply)

result = helloworld_svc.say_hello( "A" * 10_000), compression: "gzip") #=> HelloReply: "Hello AAAAAAAA..."

Note: The :compression option can also be set in the rpc call. If true, it’ll use gzip. Make sure that you load any non-default compression (such as the :brotli plugin) if you want and can using it.


Server call cancellations will raise an HTTPX::GRPCError exception. This exception contains two important attributes:

  • status (Integer): the GRPC error code.
  • message (String): the corresponding error message.

Clients will also cancel the call if some error happens while writing the request, i.e. if some error happens while enumerating the request messagesç


grpc.build_stub("localhost:4545"), as is, generates an “insecure” stub, which uses unencrypted HTTP/2 streams. In order to encrypt the channel, you can use with_channel_credentials:

helloworld_stub = grpc.with_channel_credentials("path/to/ca.pem").build_stub("localhost:4545")

# or, for TLS client authentication, you can also pass the client key/cert:

helloworld_stub = grpc.with_channel_credentials("path/to/ca.pem", "path/to/key", "path/to/cert").build_stub("localhost:4545")

For call authentication, you can set the call_credentials option when performing the call:

call_credentials = -> {
  jwt = googleauth.generate_special_token
  { "x-special-auth-header" => jwt }

result = helloworld_svc.with_call_credentials(call_credentials).say_hello( "Jack")) #=> HelloReply: "Hello Jack"


The following options can be set when the grpc plugin is loaded:

  • :grpc_deadline (Integer, default is 60): number of seconds in which the call needs to be answered before it is cancelled.
  • :call_credentials (responds to #call): callback triggered to inject authentication metadata.

Next: Response Cache