One keyword โ stream โ on either side of the arrow gives you four call shapes. Each is still a single HTTP/2 stream underneath.
Put stream before the request type, the response type, both, or neither. That's the entire taxonomy:
service ChatService {
rpc GetProfile(ProfileRequest) returns (Profile); // unary
rpc ListMessages(RoomRequest) returns (stream Message); // server streaming
rpc UploadPhotos(stream PhotoChunk) returns (UploadSummary); // client streaming
rpc Chat(stream ChatEvent) returns (stream ChatEvent); // bidirectional
}
The ordinary function call. One message in, one message out. 90% of your methods.
# Server
class ChatService(chat_pb2_grpc.ChatServiceServicer):
def GetProfile(self, request, context):
return chat_pb2.Profile(user_id=request.user_id, name="Ada")
# Client
resp = stub.GetProfile(chat_pb2.ProfileRequest(user_id="u1"))
print(resp.name)
Client sends one request; server writes a sequence of messages, then closes. Great for large result sets, feeds, and progress updates โ the client processes each item as it lands instead of waiting for the whole batch.
# Server: yield each message
def ListMessages(self, request, context):
for msg in db.iter_messages(request.room_id):
yield chat_pb2.Message(id=msg.id, body=msg.body)
# Client: iterate the response
for msg in stub.ListMessages(chat_pb2.RoomRequest(room_id="r1")):
print(msg.body)
Client writes a sequence, server responds once at the end. Ideal for uploads, batch ingestion, or aggregations where the server only needs the final answer.
# Server: request_iterator drains the client's stream
def UploadPhotos(self, request_iterator, context):
total = 0
for chunk in request_iterator:
total += len(chunk.data)
return chat_pb2.UploadSummary(bytes_received=total)
# Client: pass an iterator/generator of requests
def gen():
for part in read_file_in_chunks("photo.jpg"):
yield chat_pb2.PhotoChunk(data=part)
summary = stub.UploadPhotos(gen())
print(summary.bytes_received)
Both sides read and write independently on the same stream, in any interleaving. This is full-duplex: the server can push before the client finishes sending. Chat, live collaboration, and streaming RPC pipelines.
# Server: read and write concurrently on one context
def Chat(self, request_iterator, context):
for event in request_iterator: # incoming from client
for out in room.route(event): # may fan out
yield out # outgoing to client
# Client: send a generator, receive an iterator, both live at once
responses = stub.Chat(outgoing_events())
for event in responses:
render(event)
| Type | .proto | Client sends | Server sends | Use for |
|---|---|---|---|---|
| Unary | (Req) returns (Res) | 1 | 1 | Normal calls |
| Server stream | (Req) returns (stream Res) | 1 | N | Feeds, large results |
| Client stream | (stream Req) returns (Res) | N | 1 | Uploads, aggregation |
| Bidirectional | (stream Req) returns (stream Res) | N | N | Chat, live sync |