Four call shapes from one stream — half-close and DATA frames on HTTP/2
Unary and streaming calls are the SAME HTTP/2 stream — the only difference is how many DATA frames flow each way and when each side half-closes — so streaming is a consequence of the transport, not a bolt-on.
Last scene, an RPC became just a stream of DATA frames on one connection. Once an RPC is only a COUNT of DATA frames in each direction, changing that count is free — so the same stream that carried one greet("Ada") can carry a whole flow of them.
Scene 05
Four call shapes from one stream
- Watch
- Try it
- Predict
- Capture
Here is the running example you've been carrying — greet("Ada") — drawn on one HTTP/2 stream (a stream is one RPC's lane down the shared connection; a frame is one chunk on it). First the simplest shape: the client sends ONE request DATA frame, the server sends back ONE response DATA frame ("Hello Ada"), and both sides drop a half-close flag — that's the whole call. When a client sends one request and gets exactly one response back, we call it a unary call — the plain function-call shape RPC started from. Now advance: the SAME stream re-draws with the server sending THREE ordered response frames ("Hello Ada", "Hi again Ada", "Bye Ada") before it half-closes. Nothing about the transport changed — it's one stream either way; the server just sent more DATA frames one direction. A call where one side (or both) sends many DATA frames instead of one is a streaming call. The pattern of arrows is the only thing that moved.
Highlighted lines are the ones running in the diagram right now.
def call(method, requests):stream = conn.newStream() # one RPC = one streamstream.send(HEADERS, method, grpc_timeout)for req in requests: # 1 frame, or manystream.send(DATA, encode(req))stream.send(END_STREAM) # half-close: done sendingfor resp in stream.recv(): # frames arrive in orderyield decode(resp)
def handle(stream):requests = [decode(f) for f in stream.recv()]for resp in compute(requests): # 1 frame, or manystream.send(DATA, encode(resp))stream.send(END_STREAM) # half-close: done sending
def send(kind, payload=None):if kind == END_STREAM:self.flags |= END_STREAM # one direction closedreturn# gRPC length-prefix: 1 flag byte + 4-byte lengthframe = lengthPrefix(payload)conn.writeFrame(self.id, kind, frame)# same stream id, same path — for all four shapes
Where this sits in Build a gRPC-style RPC framework
Scene 05 of 14, in the The transport act — HTTP/2 streams and the four shapes of a call.. Unary and the three streaming shapes are the same stream — only the number and direction of DATA frames differ. Streaming is a consequence of the transport, not a bolt-on.
Up next. Whether a call sends one frame or a thousand, it runs as long as it likes if nothing stops it — and a server happily computing for a client who already walked away is the most expensive bug in the whole stack.
All 14 scenes in Build a gRPC-style RPC framework · Every curriculum