Implementing gRPC APIs with Protocol Buffers
Scope: gRPC services, Protocol Buffers, streaming, error handling, performance optimization Lines: ~350 Last Updated: 2025-10-27
Activate this skill when:
gRPC (gRPC Remote Procedure Call): High-performance RPC framework using HTTP/2 and Protocol Buffers.
Key characteristics:
Architecture components:
Client → Stub (Generated) → HTTP/2 → Server → Service Implementation
↓ ↓
Interceptors Interceptors
↓ ↓
Metadata Metadata
// users.proto
syntax = "proto3";
package users.v1;
option go_package = "github.com/example/users/v1;usersv1";
// User represents a user in the system
message User {
string id = 1; // Field number (used for encoding)
string email = 2;
string name = 3;
int32 age = 4;
bool is_active = 5;
repeated string tags = 6; // List of strings
google.protobuf.Timestamp created_at = 7;
}
// CreateUserRequest with nested message
message CreateUserRequest {
string email = 1;
string name = 2;
Profile profile = 3; // Nested message
message Profile {
string bio = 1;
string avatar_url = 2;
}
}
message CreateUserResponse {
User user = 1;
string message = 2;
}
Key concepts:
string, int32, int64, bool, bytes, float, doubleimport "google/protobuf/timestamp.proto";// Four types of RPCs
service UserService {
// Unary: Single request → Single response
rpc GetUser(GetUserRequest) returns (GetUserResponse);
// Server streaming: Single request → Stream of responses
rpc ListUsers(ListUsersRequest) returns (stream User);
// Client streaming: Stream of requests → Single response
rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);
// Bidirectional streaming: Stream of requests ↔ Stream of responses
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
message GetUserRequest {
string id = 1;
}
message GetUserResponse {
User user = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message CreateUsersResponse {
repeated User users = 1;
int32 created_count = 2;
}
message ChatMessage {
string user_id = 1;
string content = 2;
google.protobuf.Timestamp timestamp = 3;
}
Pattern: Client sends one request, server returns one response
.proto definition:
rpc GetUser(GetUserRequest) returns (GetUserResponse);
Python server (grpcio):
import grpc
from concurrent import futures
import users_pb2
import users_pb2_grpc
class UserService(users_pb2_grpc.UserServiceServicer):
def GetUser(self, request, context):
# request: GetUserRequest
# context: grpc.ServicerContext (metadata, peer, etc.)
user_id = request.id
# Fetch from database
user = db.get_user(user_id)
if not user:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details(f'User {user_id} not found')
return users_pb2.GetUserResponse()
# Return response
return users_pb2.GetUserResponse(
user=users_pb2.User(
id=user['id'],
email=user['email'],
name=user['name']
)
)
# Start server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
users_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
Python client:
import grpc
import users_pb2
import users_pb2_grpc
# Create channel
channel = grpc.insecure_channel('localhost:50051')
stub = users_pb2_grpc.UserServiceStub(channel)
# Call RPC
try:
response = stub.GetUser(users_pb2.GetUserRequest(id='123'))
print(f'User: {response.user.name}')
except grpc.RpcError as e:
print(f'Error: {e.code()} - {e.details()}')
Pattern: Client sends one request, server streams multiple responses
.proto definition:
rpc ListUsers(ListUsersRequest) returns (stream User);
Python server:
def ListUsers(self, request, context):
page_size = request.page_size or 50
# Stream users (yield multiple responses)
for user in db.list_users(limit=page_size):
yield users_pb2.User(
id=user['id'],
email=user['email'],
name=user['name']
)
Python client:
# Receive stream
response_stream = stub.ListUsers(users_pb2.ListUsersRequest(page_size=10))
for user in response_stream:
print(f'User: {user.name} ({user.email})')
Pattern: Client streams multiple requests, server returns one response
.proto definition:
rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);
Python server:
def CreateUsers(self, request_iterator, context):
created_users = []
# Receive stream of requests
for request in request_iterator:
user = db.create_user(email=request.email, name=request.name)
created_users.append(user)
return users_pb2.CreateUsersResponse(
users=created_users,
created_count=len(created_users)
)
Python client:
def request_generator():
# Generate stream of requests
for i in range(5):
yield users_pb2.CreateUserRequest(
email=f'user{i}@example.com',
name=f'User {i}'
)
# Send stream
response = stub.CreateUsers(request_generator())
print(f'Created {response.created_count} users')
Pattern: Client and server both stream messages independently
.proto definition:
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
Python server:
def Chat(self, request_iterator, context):
# Concurrent reading and writing
for message in request_iterator:
# Process incoming message
user_id = message.user_id
content = message.content
# Broadcast to other users (example)
response = users_pb2.ChatMessage(
user_id='system',
content=f'{user_id} said: {content}',
timestamp=Timestamp()
)
yield response
Python client:
def message_generator():
messages = ['Hello', 'How are you?', 'Goodbye']
for msg in messages:
yield users_pb2.ChatMessage(
user_id='user123',
content=msg,
timestamp=Timestamp()
)
# Bidirectional stream
responses = stub.Chat(message_generator())
for response in responses:
print(f'{response.user_id}: {response.content}')
from grpc import StatusCode
# Common status codes
StatusCode.OK # 0: Success
StatusCode.CANCELLED # 1: Operation cancelled
StatusCode.INVALID_ARGUMENT # 3: Invalid argument
StatusCode.NOT_FOUND # 5: Resource not found
StatusCode.ALREADY_EXISTS # 6: Resource already exists
StatusCode.PERMISSION_DENIED # 7: Permission denied
StatusCode.UNAUTHENTICATED # 16: Missing authentication
StatusCode.INTERNAL # 13: Internal error
StatusCode.UNAVAILABLE # 14: Service unavailable
StatusCode.DEADLINE_EXCEEDED # 4: Deadline exceeded
def GetUser(self, request, context):
try:
user_id = request.id
# Validate input
if not user_id:
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
context.set_details('User ID is required')
return users_pb2.GetUserResponse()
# Check authentication
if not context.metadata().get('authorization'):
context.set_code(grpc.StatusCode.UNAUTHENTICATED)
context.set_details('Authentication required')
return users_pb2.GetUserResponse()
# Fetch user
user = db.get_user(user_id)
if not user:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details(f'User {user_id} not found')
return users_pb2.GetUserResponse()
return users_pb2.GetUserResponse(user=user)
except Exception as e:
context.set_code(grpc.StatusCode.INTERNAL)
context.set_details(f'Internal error: {str(e)}')
return users_pb2.GetUserResponse()
try:
response = stub.GetUser(
users_pb2.GetUserRequest(id='123'),
timeout=5 # Deadline in seconds
)
print(f'User: {response.user.name}')
except grpc.RpcError as e:
# Handle specific errors
if e.code() == grpc.StatusCode.NOT_FOUND:
print('User not found')
elif e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
print('Request timeout')
elif e.code() == grpc.StatusCode.UNAUTHENTICATED:
print('Authentication required')
else:
print(f'Error: {e.code()} - {e.details()}')
class LoggingInterceptor(grpc.ServerInterceptor):
def intercept_service(self, continuation, handler_call_details):
# Before RPC
method = handler_call_details.method
print(f'[Server] Received: {method}')
# Continue to actual RPC
response = continuation(handler_call_details)
# After RPC
print(f'[Server] Completed: {method}')
return response
# Use interceptor
server = grpc.server(
futures.ThreadPoolExecutor(max_workers=10),
interceptors=[LoggingInterceptor()]
)
class AuthInterceptor(grpc.UnaryUnaryClientInterceptor):
def __init__(self, token):
self.token = token
def intercept_unary_unary(self, continuation, client_call_details, request):
# Add authentication metadata
metadata = []
if client_call_details.metadata:
metadata = list(client_call_details.metadata)
metadata.append(('authorization', f'Bearer {self.token}'))
# Update call details
new_details = client_call_details._replace(metadata=metadata)
# Continue with modified metadata
return continuation(new_details, request)
# Use interceptor
channel = grpc.insecure_channel('localhost:50051')
intercepted_channel = grpc.intercept_channel(
channel,
AuthInterceptor(token='secret-token')
)
stub = users_pb2_grpc.UserServiceStub(intercepted_channel)
Always set deadlines to prevent hanging requests:
# Client-side deadline
response = stub.GetUser(
request,
timeout=5 # 5 seconds
)
# Server-side deadline check
def GetUser(self, request, context):
if context.time_remaining() < 1: # Less than 1 second left
context.set_code(grpc.StatusCode.DEADLINE_EXCEEDED)
return users_pb2.GetUserResponse()
# Client: Send metadata
metadata = [
('authorization', 'Bearer token123'),
('request-id', 'abc-123')
]
response = stub.GetUser(request, metadata=metadata)
# Server: Read metadata
def GetUser(self, request, context):
metadata = dict(context.invocation_metadata())
auth_token = metadata.get('authorization')
request_id = metadata.get('request-id')
# Reuse channels (don't create per-request)
channel = grpc.insecure_channel('localhost:50051')
stub = users_pb2_grpc.UserServiceStub(channel)
# Close when done
channel.close()
# Connection options
channel = grpc.insecure_channel(
'localhost:50051',
options=[
('grpc.max_receive_message_length', 10 * 1024 * 1024), # 10 MB
('grpc.max_send_message_length', 10 * 1024 * 1024), # 10 MB
('grpc.keepalive_time_ms', 30000), # 30 seconds
]
)
❌ Not setting deadlines: Requests can hang forever ✅ Set timeouts: stub.GetUser(request, timeout=5)
❌ Creating channels per request: Expensive (TCP connection overhead) ✅ Reuse channels: Create once, use many times
❌ Ignoring status codes: All errors return same generic message ✅ Handle specific codes: Check grpc.StatusCode.NOT_FOUND, etc.
❌ Large messages: Sending 100 MB+ messages ✅ Use streaming: Break into smaller chunks
❌ No error handling in streams: Stream errors kill connection ✅ Wrap in try/except: Handle grpc.RpcError
❌ Missing field numbers: Can't decode old messages ✅ Never reuse field numbers: Mark as reserved
❌ Breaking changes: Removing required fields ✅ Use proto evolution: Add fields, use reserved, deprecate
protobuf-schema-design.md - Advanced Protocol Buffers patternsgrpc-load-balancing.md - Client-side and proxy load balancinggrpc-security.md - TLS, authentication, authorizationhttp2-fundamentals.md - Understanding HTTP/2 (gRPC transport)api-rest-design.md - Comparing REST vs gRPCThis skill includes comprehensive Level 3 resources for deep gRPC implementation knowledge and practical tools.
Resources include:
Location: skills/protocols/grpc-implementation/resources/REFERENCE.md
Comprehensive technical reference (2,303 lines) covering:
Core Topics:
Key Sections:
Format: Markdown with extensive code examples in Python, Go, and Node.js
Three production-ready executable scripts in resources/scripts/:
Purpose: Validate Protocol Buffer definitions
Features:
Usage:
# Basic validation
./validate_proto.py --proto-file api.proto
# JSON output
./validate_proto.py --proto-file api.proto --json
# Check breaking changes
./validate_proto.py --proto-file api_v2.proto --check-breaking --baseline api_v1.proto
# Save report
./validate_proto.py --proto-file api.proto --json > validation-report.json
Categories checked:
Purpose: Generate gRPC client code and examples
Features:
Usage:
# Generate Python client
./generate_client.py --proto-file api.proto --language python --output-dir ./client
# Generate Go client
./generate_client.py --proto-file api.proto --language go --output-dir ./client
# JSON output (list generated files)
./generate_client.py --proto-file api.proto --language python --json
Generated files:
Includes: Error handling, retry logic, connection management, interceptors
Purpose: Test gRPC server endpoints and performance
Features:
Usage:
# Test all methods
./test_grpc_server.sh --server localhost:50051 --proto-file api.proto
# Test specific method
./test_grpc_server.sh --server localhost:50051 --proto-file api.proto --method UserService/GetUser
# With metadata (authentication)
./test_grpc_server.sh --server localhost:50051 --proto-file api.proto --metadata authorization:"Bearer token123"
# JSON output for CI/CD
./test_grpc_server.sh --server localhost:50051 --proto-file api.proto --json > report.json
# Performance test (100 iterations)
./test_grpc_server.sh --server localhost:50051 --proto-file api.proto --iterations 100
Requirements: grpcurl, jq, bc
Metrics: Latency (min, avg, p50, p95, p99, max), throughput (req/sec), success rate
Eight production-ready examples in resources/examples/:
Complete service definition demonstrating:
Services: UserService with 10 methods covering all RPC patterns
Complete Python gRPC server implementation:
Key features: Unary CRUD, server streaming (ListUsers, WatchUserChanges), client streaming (CreateUsers, UploadUserData), bidirectional (Chat, CollaborativeEdit)
Complete Python gRPC client implementation:
Tests: Unary RPCs, server streaming, client streaming, bidirectional streaming, error handling
Go gRPC server implementation (template):
Format: Production-ready Go server template
Node.js gRPC server implementation (template):
Format: Production-ready Node.js server template
Authentication interceptor example:
Features: JWT authentication, token validation, expiration handling, public endpoint support
Bidirectional streaming example (template):
Pattern: Full duplex communication for real-time chat
Production Docker deployment:
Services: grpc-server (Python), grpc-client (Python), grpcurl (testing)
Features: Health checks, service discovery, logging, production notes for TLS/auth
1. Validate your .proto file:
cd skills/protocols/grpc-implementation/resources/scripts
./validate_proto.py --proto-file ../examples/protos/service.proto --json
2. Generate client code:
./generate_client.py --proto-file ../examples/protos/service.proto --language python --output-dir ./client
3. Run examples:
cd ../examples
# Generate stubs
python -m grpc_tools.protoc -I./protos --python_out=./python --grpc_python_out=./python protos/service.proto
# Start server
python python/server.py
# Run client (in another terminal)
python python/client.py
4. Test with Docker:
cd docker
docker-compose up
5. Test server:
cd scripts
./test_grpc_server.sh --server localhost:50051 --proto-file ../examples/protos/service.proto --json
skills/protocols/grpc-implementation/
├── grpc-implementation.md (this file)
└── resources/
├── REFERENCE.md (2,303 lines)
├── scripts/
│ ├── validate_proto.py (687 lines) - Proto validation
│ ├── generate_client.py (906 lines) - Client generation
│ └── test_grpc_server.sh (643 lines) - Server testing
└── examples/
├── protos/
│ └── service.proto - Complete service definition
├── python/
│ ├── server.py - Python server (all RPC types)
│ └── client.py - Python client (all RPC types)
├── go/
│ └── server.go - Go server template
├── nodejs/
│ └── server.js - Node.js server template
├── interceptors/
│ └── auth_interceptor.py - JWT authentication
├── streaming/
│ └── bidirectional_chat.py - Chat example
└── docker/
└── docker-compose.yml - Docker deployment
| Category | Item | Lines | Description | |----------|------|-------|-------------| | Reference | REFERENCE.md | 2,303 | Complete technical reference | | Scripts | validate_proto.py | 687 | Proto validation tool | | | generate_client.py | 906 | Client code generator | | | test_grpc_server.sh | 643 | Server testing tool | | Examples | service.proto | 156 | Complete service definition | | | python/server.py | 450 | Python server implementation | | | python/client.py | 280 | Python client implementation | | | auth_interceptor.py | 240 | JWT authentication example | | | docker-compose.yml | 90 | Docker deployment |
Total: 5,755 lines of production-ready resources
Last Updated: 2025-10-27 Format Version: 1.0 (Atomic)