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.gRPCServiceCallExceptionType

Exception 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:

  1. grpc_status::Int - See here for an indepth explanation of each status.
  2. message::String
source

Package Initialization / Shutdown

gRPCClient2.grpc_initMethod
grpc_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)

source
gRPCClient2.grpc_shutdownMethod
grpc_shutdown()

Shuts down the global gRPCCURL state. This neatly cleans up all active connections and requests. Useful for calling during development with Revise.

source
gRPCClient2.grpc_global_handleMethod
grpc_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.

source

Request Functions

gRPCClient2.grpc_sync_requestMethod
grpc_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

source
gRPCClient2.grpc_async_requestMethod
grpc_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.

source
gRPCClient2.grpc_async_awaitMethod
grpc_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.

source
gRPCClient2.grpc_async_requestMethod
grpc_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
source