Quick Start
This guide demonstrates how to create a gRPC server in Julia using gRPCServer.jl.
Prerequisites
- Julia 1.10 or later
- ProtoBuf.jl for message type generation
Installation
using Pkg
Pkg.dev("https://github.com/s-celles/gRPCServer.jl")
# Pkg.add("gRPCServer") # when registeredStep 1: Define Your Service
This example is based on examples/01_hello_world (basic unary) and examples/02_hello_stream (streaming).
Create a .proto file defining your service:
// greeter.proto
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc SayHelloStream (HelloRequest) returns (stream HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}Step 2: Generate Julia Types
Use ProtoBuf.jl to generate Julia types directly from the .proto file (no external tools needed):
using ProtoBuf
# Generate Julia structs from proto file
# Arguments: proto_file, search_path, output_directory
protojl("greeter.proto", ".", "generated")This creates the following file structure:
generated/
└── helloworld/
├── helloworld.jl # Module with message types
└── greeter_pb.jl # Generated protobuf definitionsThe helloworld.jl file exports HelloRequest and HelloReply structs that you'll use in your handlers.
Step 3: Implement Handlers
using gRPCServer
include("generated/helloworld/helloworld.jl")
using .helloworld
# Unary RPC handler
function say_hello(ctx::ServerContext, request::HelloRequest)::HelloReply
name = isempty(request.name) ? "World" : request.name
return HelloReply(message = "Hello, $(name)!")
end
# Server streaming RPC handler
function say_hello_stream(
ctx::ServerContext,
request::HelloRequest,
stream::ServerStream{HelloReply}
)::Nothing
name = isempty(request.name) ? "World" : request.name
for i in 1:5
send!(stream, HelloReply(message = "Hello $(i), $(name)!"))
sleep(0.5) # Simulate work
end
return nothing
endStep 4: Create Service Descriptor
struct GreeterService end
function gRPCServer.service_descriptor(::GreeterService)
ServiceDescriptor(
"helloworld.Greeter",
Dict(
"SayHello" => MethodDescriptor(
"SayHello",
MethodType.UNARY,
"helloworld.HelloRequest",
"helloworld.HelloReply",
say_hello
),
"SayHelloStream" => MethodDescriptor(
"SayHelloStream",
MethodType.SERVER_STREAMING,
"helloworld.HelloRequest",
"helloworld.HelloReply",
say_hello_stream
)
),
nothing # File descriptor for reflection (optional)
)
endStep 5: Create and Run Server
# Create server
host = "127.0.0.1"
port = 50051
server = GRPCServer(host, port)
# Register service
register!(server, GreeterService())
# Start server (blocking)
@info "Starting gRPC server" host=host port=port
run(server)When the server starts successfully, you'll see:
[ Info: gRPC server starting
│ host = "127.0.0.1"
│ port = 50051The server is now listening for connections. Keep this terminal running.
Complete Example
Save as server.jl:
using gRPCServer
# Include generated types
include("generated/helloworld/helloworld.jl")
using .helloworld
# Handlers
function say_hello(ctx::ServerContext, request::HelloRequest)::HelloReply
@info "Received request" name=request.name request_id=ctx.request_id
HelloReply(message = "Hello, $(request.name)!")
end
function say_hello_stream(
ctx::ServerContext,
request::HelloRequest,
stream::ServerStream{HelloReply}
)::Nothing
for i in 1:5
if ctx.cancelled
@warn "Stream cancelled by client"
return nothing
end
send!(stream, HelloReply(message = "Hello $(i), $(request.name)!"))
sleep(0.5)
end
return nothing
end
# Service definition
struct GreeterService end
function gRPCServer.service_descriptor(::GreeterService)
ServiceDescriptor(
"helloworld.Greeter",
Dict(
"SayHello" => MethodDescriptor(
"SayHello", MethodType.UNARY,
"helloworld.HelloRequest", "helloworld.HelloReply",
say_hello
),
"SayHelloStream" => MethodDescriptor(
"SayHelloStream", MethodType.SERVER_STREAMING,
"helloworld.HelloRequest", "helloworld.HelloReply",
say_hello_stream
)
),
nothing
)
end
# Run server
function main()
host = "127.0.0.1" # 0.0.0.0 (risky)
port = 50051
server = GRPCServer(host, port;
enable_health_check = true,
enable_reflection = true
)
register!(server, GreeterService())
@info "gRPC server starting" host=host port=port
run(server)
end
main()Run with:
julia server.jlTesting with grpcurl
Note: grpcurl is a command-line tool for interacting with gRPC servers. See the installation instructions for your platform.
# List services (requires reflection enabled)
grpcurl -plaintext localhost:50051 listExpected output:
grpc.health.v1.Health
grpc.reflection.v1alpha.ServerReflection
helloworld.Greeter# Call unary RPC
grpcurl -plaintext -d '{"name": "Julia"}' \
localhost:50051 helloworld.Greeter/SayHelloExpected output:
{
"message": "Hello, Julia!"
}# Call streaming RPC
grpcurl -plaintext -d '{"name": "Julia"}' \
localhost:50051 helloworld.Greeter/SayHelloStreamExpected output (5 messages streamed):
{
"message": "Hello 1, Julia!"
}
{
"message": "Hello 2, Julia!"
}
{
"message": "Hello 3, Julia!"
}
{
"message": "Hello 4, Julia!"
}
{
"message": "Hello 5, Julia!"
}Testing with gRPCClient.jl
using gRPCClient
include("generated/helloworld.jl")
using .helloworld
# Create channel
channel = gRPCClient.Channel("localhost", 50051)
# Create stub
stub = GreeterStub(channel)
# Call unary RPC
response = stub.SayHello(HelloRequest(name = "Julia"))
println(response.message) # "Hello, Julia!"
# Call streaming RPC
for reply in stub.SayHelloStream(HelloRequest(name = "Julia"))
println(reply.message)
endAdding Interceptors
# Add built-in logging interceptor
add_interceptor!(server, LoggingInterceptor())
# Add metrics interceptor with callbacks
add_interceptor!(server, MetricsInterceptor(
on_request = (method, size) -> increment_counter("requests"),
on_response = (method, status, ms, size) -> record_latency(ms)
))Enabling TLS
host = "127.0.0.1"
port = 50051
tls_config = TLSConfig(
cert_chain = "/path/to/server.crt",
private_key = "/path/to/server.key",
client_ca = nothing, # Set for mTLS
require_client_cert = false,
min_version = :TLSv1_2,
alpn_protocols = ["h2"], # Default; shown for clarity
)
server = GRPCServer(host, port;
tls = tls_config
)See the TLS documentation page for a full walkthrough including ALPN behavior, mTLS, and certificate reload.
Error Handling
function my_handler(ctx::ServerContext, request)
if !is_valid(request)
throw(GRPCError(
StatusCode.INVALID_ARGUMENT,
"Request validation failed",
[]
))
end
user = find_user(request.user_id)
if user === nothing
throw(GRPCError(
StatusCode.NOT_FOUND,
"User not found: $(request.user_id)",
[]
))
end
return MyResponse(user = user)
endGraceful Shutdown
# In a separate task or signal handler
function shutdown(server::GRPCServer)
@info "Initiating graceful shutdown..."
stop!(server; timeout = 30.0) # Wait up to 30s for in-flight requests
@info "Server stopped"
end