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.
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 stream3 stream.send(HEADERS, method, grpc_timeout)4 for req in requests: # 1 frame, or many5 stream.send(DATA, encode(req))6 stream.send(END_STREAM) # half-close: done sending7 for resp in stream.recv(): # frames arrive in order8 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 many4 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 closed4 return5 # gRPC length-prefix: 1 flag byte + 4-byte length6 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.