Build a gRPC-style RPC framework (14 scenes)
Scene 05 · Four call shapes from one stream
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.
Previously

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
Diagram
One horizontal HTTP/2 stream (the pipe) with a client end on the left and a server end on the right. Request DATA frames ride the top track pointing right; response DATA frames ride the bottom track pointing left. A half-close flag drops on a side once it has sent its last frame. A UNARY CALL is one request frame and one response frame; a STREAMING CALL is the same stream with many DATA frames flowing one or both ways. The three streaming variants — server-streaming (one →, many ←), client-streaming (many →, one ←), and bidirectional (many ↔ many, the two directions independent) — are just different DATA-frame patterns on this same pipe.
CALL SHAPEUnaryone → · one ←same HTTP/2 stream — only the DATA-frame pattern changesclientsends requestsserversends responsesSTREAM 1REQUEST FRAMES →← RESPONSE FRAMESDATAgreet("Ada")DATAHello Adahalf-closehalf-close11One request frame, one response frame — the classic call.
unary call: one → , one ←
half-close: a side raises this once it's done sending
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.
Implementation
Client.call
open one stream, send request frame(s), half-close
1def call(method, requests):
2 stream = conn.newStream() # one RPC = one stream
3 stream.send(HEADERS, method, grpc_timeout)
4 for req in requests: # 1 frame, or many
5 stream.send(DATA, encode(req))
6 stream.send(END_STREAM) # half-close: done sending
7 for resp in stream.recv(): # frames arrive in order
8 yield decode(resp)
Server.handle
read request frame(s), write response frame(s), half-close
1def handle(stream):
2 requests = [decode(f) for f in stream.recv()]
3 for resp in compute(requests): # 1 frame, or many
4 stream.send(DATA, encode(resp))
5 stream.send(END_STREAM) # half-close: done sending
Stream.sendFrame
the single transport routine every shape rides
1def send(kind, payload=None):
2 if kind == END_STREAM:
3 self.flags |= END_STREAM # one direction closed
4 return
5 # gRPC length-prefix: 1 flag byte + 4-byte length
6 frame = lengthPrefix(payload)
7 conn.writeFrame(self.id, kind, frame)
8 # same stream id, same path — for all four shapes
Not sure what to ask? Tap a question — the staff engineer answers in the chat panel.