the :grpc
plugin provides a DSL for declaring GRPC clients and perform GRPC calls via httpx
and its HTTP/2
“bus”.
It supports:
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 RPC
helloworld_svc = helloworld_stub.rpc(:SayHello, HelloRequest, HelloReply)
result = helloworld_svc.say_hello(HelloRequest.new(name: "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, client and bidi stream RPCs respectively
routeguide_svc = routeguide_stub.rpc(:RouteGuide, ListFeatures, Feature, stream: true)
result = routeguide_svc.list_features(
Rectangle.new(
lo: Point.new(latitude: 400_000_000, longitude: -750_000_000),
hi: Point.new(latitude: 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 '#{r.name}' at #{r.location.inspect}"
end
# and now you can ask for the trailing metadata
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 = SleepingEnumerator.new(ROUTE_CHAT_NOTES, 1)
reqs = RandomRoute.new(features, points_on_route)
resp = routeguide_svc.record_route(reqs.method(:each))
puts "summary: #{resp.inspect}"
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 = SleepingEnumerator.new(ROUTE_CHAT_NOTES, 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(HelloRequest.new(name: "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(HelloRequest.new(name: "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