gRPCClient2.jl
gRPCClient2.jl aims to be a production grade gRPC client emphasizing performance and reliability.
Features
- Unary RPC (non streaming)
- HTTP/2 connection multiplexing
- Synchronous and asynchronous interfaces
- Thread safe
- SSL/TLS
The client is missing a few features which will be added over time:
- OAuth2
- Compression
- Streaming RPC
Getting Started
Test gRPC Server
All examples in the documentation are run against a test server written in Python. You can run it by doing the following:
# Install uv package manager - see https://docs.astral.sh/uv/#installation for more details
curl -LsSf https://astral.sh/uv/install.sh | sh
# Change directory to the python test server project
cd test/python
# Run the test server
uv run grpc_test_server.py
Code Generation
Note: this is currently disabled due to blocking issues in ProtoBuf.jl. See here for more information.
gRPCClient2.jl integrates with ProtoBuf.jl to automatically generate Julia client stubs for calling gRPC.
using ProtoBuf
using gRPCClient2
# Creates Julia bindings for the messages and RPC defined in test.proto
protojl("test/proto/test.proto", ".", "test/gen")
Making Requests with gRPCClient2.jl
using gRPCClient2
# Include the generated bindings
include("test/gen/test/test_pb.jl")
# Create a client bound to a specific RPC
client = TestService_TestRPC_Client("localhost", 8001)
# Make a syncronous request and get back a TestResponse
response = grpc_sync_request(client, TestRequest(1, zeros(UInt64, 1)))
@info response
# Make some async requests and await their TestResponse
requests = Vector{gRPCRequest}()
for i in 1:10
push!(
requests,
grpc_async_request(client, TestRequest(1, zeros(UInt64, 1)))
)
end
for request in requests
response = grpc_async_await(client, request)
@info response
end
Exceptions
gRPCClient2.gRPCServiceCallException
— TypeException type that is thrown when something goes wrong while calling an RPC. This can either be triggered by the servers response code or by the client when something fails.
This exception type has two fields:
grpc_status::Int
- See here for an indepth explanation of each status.message::String
Package Initialization / Shutdown
gRPCClient2.grpc_init
— Methodgrpc_init()
Initializes the global gRPCCURL
state. This should be called once before making gRPC calls. There is no harm in calling this more than once (ie by different packages/dependencies)
gRPCClient2.grpc_shutdown
— Methodgrpc_shutdown()
Shuts down the global gRPCCURL
state. This neatly cleans up all active connections and requests. Useful for calling during development with Revise.
gRPCClient2.grpc_global_handle
— Methodgrpc_global_handle()
Returns the global gRPCCURL
state which contains a libCURL multi handle. By default all gRPC functions use this multi in order to ensure that HTTP/2 multiplexing happens where possible.
Request Functions
gRPCClient2.grpc_sync_request
— Methodgrpc_sync_request(client::gRPCClient{TRequest,TResponse}, request::TRequest) where {TRequest<:Any,TResponse<:Any}
Do a synchronous gRPC request: send the request and wait for the response before returning it. Under the hood this just calls grpc_async_request
and grpc_async_await
gRPCClient2.grpc_async_request
— Methodgrpc_async_request(client::gRPCClient{TRequest,TResponse}, request::TRequest) where {TRequest<:Any,TResponse<:Any}
Initiate an asynchronous gRPC request: send the request to the server and then immediately return a gRPCRequest
object without waiting for the response. In order to wait on / retrieve the result once its ready, call grpc_async_await
. This is ideal when you need to send many requests in parallel and waiting on each response before sending the next request would things down.
gRPCClient2.grpc_async_await
— Methodgrpc_async_await(client::gRPCClient{TRequest,TResponse}, request::gRPCRequest) where {TRequest<:Any,TResponse<:Any}
Wait for the request to complete and return the response when it is ready. Throws any exceptions that were encountered during handling of the request.
gRPCClient2.grpc_async_request
— Methodgrpc_async_request(client::gRPCClient{TRequest,TResponse}, request::TRequest, channel::Channel{gRPCAsyncChannelResponse{TResponse}}, index::Int64) where {TRequest<:Any,TResponse<:Any}
Initiate an asynchronous gRPC request: send the request to the server and then immediately return. When the request is complete a background task will put the response in the provided channel. This has the advantage over the request / await patern in that you can handle responses immediately after they are recieved in any order.
using gRPCClient2
grpc_init()
include("test/gen/test/test_pb.jl")
# Connect to the test server
client = TestService_TestRPC_Client("localhost", 8001)
N = 10
channel = Channel{gRPCAsyncChannelResponse{TestResponse}}(N)
for (index, request) in enumerate([TestRequest(i, zeros(UInt64, i)) for i in 1:N])
grpc_async_request(client, request, channel, index)
end
for i in 1:N
cr = take!(channel)
# Check if an exception was thrown, if so throw it here
!isnothing(cr.ex) && throw(cr.ex)
# If this does not hold true, then the requests and responses have gotten mixed up.
@assert length(cr.response.data) == cr.index
end