Client Streaming RPC
rpc Method(stream Request) returns (Response)Client streaming sends multiple request messages on a single RPC before receiving one response. The server waits for all client messages (or processes them as they arrive) and returns a single response when the client closes the stream. Used for bulk data upload, file upload in chunks, and aggregation operations where the server accumulates input before responding.
Details
Client streaming reverses server streaming: client streams N messages → client closes stream → server responds once
HTTP/2 mechanics: the client sends multiple DATA frames, then a DATA frame with END_STREAM flag (half-close). The server processes incoming messages and sends a single DATA frame response, then HEADERS (trailers).
Processing modes: Batch mode: server waits for all messages (stream.Recv() loop until EOF), then processes and responds Streaming mode: server processes each message as it arrives, responding after the final one
Common patterns: Upload large file: client chunks file and streams chunks; server assembles and returns a checksum Bulk insert: client streams N records; server inserts and returns the count Aggregation: client streams sensor readings; server computes aggregate and responds
Error handling: the server can call stream.SendAndClose(error) to reject mid-stream if validation fails. The client receives an error response.
When NOT to use client streaming: If the server needs to process all messages before responding anyway, and messages are not large, consider batching in a single unary call with a repeated field. Client streaming adds complexity; a single request with a list is often simpler for moderate sizes (up to a few MB).
Proto definition
syntax = "proto3";
service FileService {
// Upload file in chunks, get checksum back
rpc UploadFile(stream FileChunk) returns (UploadResult);
// Stream records for bulk insert
rpc BulkInsert(stream Record) returns (InsertResult);
}
message FileChunk {
bytes data = 1;
string filename = 2; // set on first chunk
int64 offset = 3;
}
message UploadResult {
string file_id = 1;
string checksum = 2;
int64 bytes_received = 3;
}Go client streaming
// Server (Go)
func (s *FileService) UploadFile(stream pb.FileService_UploadFileServer) error {
var total int64
for {
chunk, err := stream.Recv()
if err == io.EOF {
// Client closed stream
return stream.SendAndClose(&pb.UploadResult{
BytesReceived: total,
Checksum: computeChecksum(),
})
}
if err != nil { return err }
total += int64(len(chunk.Data))
saveChunk(chunk)
}
}
// Client (Go)
stream, _ := client.UploadFile(ctx)
for _, chunk := range fileChunks {
stream.Send(&pb.FileChunk{Data: chunk})
}
result, _ := stream.CloseAndRecv() // close + get responseWhen to use
File upload in chunks
Bulk data insert
Aggregation over a stream of inputs
Sensor data collection
Backpressure
HTTP/2 flow control – client write blocks when server receive window is full.