Skip to content

Server Streaming

Server streaming is the shape where the client sends a single request and the server replies with a sequence of messages over one open stream. The client reads them one at a time until the server signals it is done.

It is the natural fit whenever the answer is not a single value but a series: watching events as they happen, streaming a large result set row by row, or reporting the progress of a long job.

sequenceDiagram
  participant C as Client
  participant S as Server
  C->>S: WatchPrices(symbol: "ACME")
  S-->>C: Price 10.20
  S-->>C: Price 10.24
  S-->>C: Price 10.19
  S-->>C: ... (until server closes)
  Note over C,S: server ends the stream, client's loop finishes
Server streaming: one request, a stream of responses

You mark the response as a stream with the stream keyword. The request stays singular:

syntax = "proto3";
package market.v1;
message WatchRequest {
string symbol = 1;
}
message Price {
string symbol = 1;
double value = 2;
int64 ts = 3;
}
service Market {
// One request, a stream of Price responses.
rpc WatchPrices(WatchRequest) returns (stream Price);
}

That single stream keyword changes the generated code: instead of returning one Price, the server gets a stream to send many, and the client gets an iterator to read many.

The server implementation receives the request, then calls “send” repeatedly. When the method returns, the stream is closed.

func (s *server) WatchPrices(req *marketv1.WatchRequest, stream marketv1.Market_WatchPricesServer) error {
for i := 0; i < 5; i++ {
price := &marketv1.Price{Symbol: req.Symbol, Value: 10 + float64(i)*0.01}
if err := stream.Send(price); err != nil {
return err // client went away
}
time.Sleep(time.Second)
}
return nil // returning closes the stream
}

The client makes one call and iterates the responses. The loop ends when the server closes the stream.

stream, _ := client.WatchPrices(ctx, &marketv1.WatchRequest{Symbol: "ACME"})
for {
price, err := stream.Recv()
if err == io.EOF {
break // server closed the stream
}
if err != nil {
log.Fatal(err)
}
fmt.Println(price.Value)
}
  • Messages arrive in order. Within a single stream, gRPC guarantees the client reads messages in the exact order the server sent them.
  • Set a deadline. A server stream can run a long time; the client should still bound it with a deadline or cancel it when it stops caring (covered in the deadlines lesson).
  • Errors can arrive mid-stream. After several good messages, the server can still end with an error status. Handle the error branch of your receive loop, not just the happy path.
  • The server should respect cancellation. If the client disconnects, Send returns an error — stop producing instead of looping forever.
What does `returns (stream Price)` change in a service method?
On the client, how does a server-streaming call normally end?
Which guarantee does gRPC make about messages within one stream?
What should a server do when the client cancels a server-streaming call?