Advanced Topics
This page covers production-ready features for gRPCServer.jl: interceptors, TLS, compression, and more.
Interceptors
Interceptors allow you to add cross-cutting concerns like logging, authentication, and rate limiting.
Logging Interceptor
host = "127.0.0.1"
port = 50051
server = GRPCServer(host, port)
add_interceptor!(server, LoggingInterceptor(
log_requests = true,
log_responses = true,
log_errors = true
))Metrics Interceptor
request_counter = Ref(0)
latencies = Float64[]
add_interceptor!(server, MetricsInterceptor(
on_request = (method, size) -> begin
request_counter[] += 1
end,
on_response = (method, status, duration_ms, size) -> begin
push!(latencies, duration_ms)
end
))Custom Authentication Interceptor
struct AuthInterceptor <: Interceptor
valid_tokens::Set{String}
end
function (auth::AuthInterceptor)(
ctx::ServerContext,
request::Any,
info::MethodInfo,
next::Function
)
token = get_metadata_string(ctx, "authorization")
if token === nothing || !(token in auth.valid_tokens)
throw(GRPCError(
StatusCode.UNAUTHENTICATED,
"Invalid or missing authentication token"
))
end
return next(ctx, request)
end
add_interceptor!(server, AuthInterceptor(Set(["token123", "token456"])))Rate Limiting Interceptor
mutable struct RateLimitInterceptor <: Interceptor
requests_per_second::Int
window_start::Float64
request_count::Int
end
RateLimitInterceptor(rps::Int) = RateLimitInterceptor(rps, time(), 0)
function (rl::RateLimitInterceptor)(
ctx::ServerContext,
request::Any,
info::MethodInfo,
next::Function
)
now = time()
if now - rl.window_start >= 1.0
rl.window_start = now
rl.request_count = 0
end
rl.request_count += 1
if rl.request_count > rl.requests_per_second
throw(GRPCError(
StatusCode.RESOURCE_EXHAUSTED,
"Rate limit exceeded"
))
end
return next(ctx, request)
end
add_interceptor!(server, RateLimitInterceptor(100))Health Checking
Health checks allow load balancers and orchestrators to monitor server status.
server = GRPCServer(host, port;
enable_health_check = true
)
# Set overall server health
set_health!(server, HealthStatus.SERVING)
# Set health for specific service
set_health!(server, "my.Service", HealthStatus.SERVING)
# Mark service as not ready (e.g., during maintenance)
set_health!(server, "my.Service", HealthStatus.NOT_SERVING)Testing Health Check
grpcurl -plaintext -d '{"service": ""}' localhost:50051 grpc.health.v1.Health/CheckExpected output:
{
"status": "SERVING"
}TLS Configuration
Basic TLS
tls_config = TLSConfig(
cert_chain = "server.crt",
private_key = "server.key"
)
server = GRPCServer(host, port; tls = tls_config)Mutual TLS (mTLS)
tls_config = TLSConfig(
cert_chain = "server.crt",
private_key = "server.key",
client_ca = "ca.crt",
require_client_cert = true
)
server = GRPCServer(host, port; tls = tls_config)Hot Reloading Certificates
# Reload certificates without restarting server
reload_tls!(server)Compression
Server-side Compression
server = GRPCServer(host, port;
enabled_compression = [CompressionCodec.GZIP, CompressionCodec.DEFLATE]
)Manual Compression
using gRPCServer: compress, decompress, CompressionCodec
data = Vector{UInt8}("Large data to compress...")
# Compress with GZIP
compressed = compress(data, CompressionCodec.GZIP)
# Decompress
original = decompress(compressed, CompressionCodec.GZIP)Error Handling
Returning Specific Status Codes
function my_handler(ctx::ServerContext, request)
if request.id < 0
throw(GRPCError(
StatusCode.INVALID_ARGUMENT,
"ID must be non-negative"
))
end
item = find_item(request.id)
if item === nothing
throw(GRPCError(
StatusCode.NOT_FOUND,
"Item not found: $(request.id)"
))
end
if !has_permission(ctx, item)
throw(GRPCError(
StatusCode.PERMISSION_DENIED,
"Access denied to item $(request.id)"
))
end
return item
endError Details
throw(GRPCError(
StatusCode.INVALID_ARGUMENT,
"Multiple validation errors",
Any[
Dict("field" => "email", "error" => "Invalid email format"),
Dict("field" => "age", "error" => "Must be positive")
]
))Context Usage
Accessing Metadata
function my_handler(ctx::ServerContext, request)
# Get string metadata
auth = get_metadata_string(ctx, "authorization")
# Get binary metadata (keys ending in -bin)
trace = get_metadata_binary(ctx, "x-trace-bin")
# Check remaining time before deadline
remaining = remaining_time(ctx)
if remaining !== nothing && remaining < 1.0
@warn "Less than 1 second remaining"
end
return response
endSetting Response Headers and Trailers
function my_handler(ctx::ServerContext, request)
# Set response header
set_header!(ctx, "x-request-id", string(ctx.request_id))
# Set trailer (sent at end of response)
set_trailer!(ctx, "x-processing-time", "50ms")
return response
endClient Streaming
A service that receives multiple requests and returns a single response:
function sum_numbers(ctx::ServerContext, stream::ClientStream{NumberRequest})
total = 0
for request in stream
total += request.value
end
return SumResponse(total = total)
end
descriptor = ServiceDescriptor(
"example.Math",
Dict(
"Sum" => MethodDescriptor(
"Sum",
MethodType.CLIENT_STREAMING,
NumberRequest,
SumResponse,
sum_numbers
)
),
nothing
)Bidirectional Streaming
A chat-like service with two-way streaming:
function chat(ctx::ServerContext, stream::BidiStream{ChatMessage, ChatMessage})
for message in stream
if is_cancelled(ctx)
break
end
# Echo back with prefix
response = ChatMessage(
user = "Server",
text = "You said: $(message.text)"
)
send!(stream, response)
end
close!(stream)
return nothing
end
descriptor = ServiceDescriptor(
"example.Chat",
Dict(
"Chat" => MethodDescriptor(
"Chat",
MethodType.BIDI_STREAMING,
ChatMessage,
ChatMessage,
chat
)
),
nothing
)Graceful Shutdown
server = GRPCServer(host, port)
register!(server, MyService())
# Run in background task
server_task = @async run(server; block = true)
# Later, initiate graceful shutdown
@info "Shutting down..."
stop!(server; timeout = 30.0)
# Wait for server to stop
wait(server_task)
@info "Server stopped"Next Steps
- API Reference - Complete API documentation
- Quick Start - Getting started guide