Skip to content

Commit b11a245

Browse files
authored
Add doc on streaming (#17)
Signed-off-by: Anuraag Agrawal <[email protected]>
1 parent b8685c8 commit b11a245

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed

docs/streaming.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Streaming
2+
3+
Connect supports several types of streaming RPCs. Streaming is exciting — it's fundamentally different from
4+
the web's typical request-response model, and in the right circumstances it can be very efficient. If you've
5+
been writing the same pagination or polling code for years, streaming may look like the answer to all your
6+
problems.
7+
8+
Temper your enthusiasm. Streaming also comes with many drawbacks:
9+
10+
- It requires excellent HTTP libraries. At the very least, the client and server must be able to stream HTTP/1.1
11+
request and response bodies. For bidirectional streaming, both parties must support HTTP/2. Long-lived streams are
12+
much more likely to encounter bugs and edge cases in HTTP/2 flow control.
13+
14+
- It requires excellent proxies. Every proxy between the server and client — including those run by cloud providers —
15+
must support HTTP/2.
16+
17+
- It weakens the protections offered to your unary handlers, since streaming typically requires proxies to be
18+
configured with much longer timeouts.
19+
20+
- It requires complex tools. Streaming RPC protocols are much more involved than unary protocols, so cURL and your
21+
browser's network inspector are useless.
22+
23+
In general, streaming ties your application more closely to your networking infrastructure and makes your application
24+
inaccessible to less-sophisticated clients. You can minimize these downsides by keeping streams short-lived.
25+
26+
All that said, `connect-python` fully supports client and server streaming. Bidirectional streaming is currently not
27+
supported for clients and requires an HTTP/2 ASGI server for servers.
28+
29+
## Streaming variants
30+
31+
In Python, streaming messages use standard `AsyncIterator` for async servers and clients, or `Iterator` for sync servers
32+
and clients.
33+
34+
In _client streaming_, the client sends multiple messages. Once the server receives all the messages, it responds with
35+
a single message. In Protobuf schemas, client streaming methods look like this:
36+
37+
```protobuf
38+
service GreetService {
39+
rpc Greet(stream GreetRequest) returns (GreetResponse) {}
40+
}
41+
```
42+
43+
In _server streaming_, the client sends a single message and the server responds with multiple messages. In Protobuf
44+
schemas, server streaming methods look like this:
45+
46+
```protobuf
47+
service GreetService {
48+
rpc Greet(GreetRequest) returns (stream GreetResponse) {}
49+
}
50+
```
51+
52+
In _bidirectional streaming_ (often called bidi), the client and server may both send multiple messages. Often, the
53+
exchange is structured like a conversation: the client sends a message, the server responds, the client sends another
54+
message, and so on. Keep in mind that this always requires end-to-end HTTP/2 support!
55+
56+
## HTTP representation
57+
58+
Streaming responses always have an HTTP status of 200 OK. This may seem unusual, but it's unavoidable: the server may
59+
encounter an error after sending a few messages, when the HTTP status has already been sent to the client. Rather than
60+
relying on the HTTP status, streaming handlers encode any errors in HTTP trailers or at the end of the response body.
61+
62+
The body of streaming requests and responses envelopes your schema-defined messages with a few bytes of
63+
protocol-specific binary framing data. Because of the interspersed framing data, the payloads are no longer valid
64+
Protobuf or JSON: instead, they use protocol-specific Content-Types like `application/connect+proto`.
65+
66+
## An example
67+
68+
Let's start by amending the `GreetService` we defined in [Getting Started](./getting-started.md) to make the Greet method use
69+
client streaming:
70+
71+
```protobuf
72+
syntax = "proto3";
73+
74+
package greet.v1;
75+
76+
message GreetRequest {
77+
string name = 1;
78+
}
79+
80+
message GreetResponse {
81+
string greeting = 1;
82+
}
83+
84+
service GreetService {
85+
rpc Greet(stream GreetRequest) returns (GreetResponse) {}
86+
}
87+
```
88+
89+
After running `buf generate` to update our generated code, we can amend our service implementation in
90+
`server.py`:
91+
92+
=== "ASGI"
93+
94+
```python
95+
from greet.v1.greet_connect import GreetService, GreetServiceASGIApplication
96+
from greet.v1.greet_pb2 import GreetResponse
97+
98+
class Greeter(GreetService):
99+
async def greet(self, request, ctx):
100+
print("Request headers: ", ctx.request_headers())
101+
greeting = ""
102+
async for message in request:
103+
greeting += f"Hello, {message.name}!\n"
104+
response = GreetResponse(greeting=greeting)
105+
ctx.response_headers()["greet-version"] = "v1"
106+
return response
107+
108+
app = GreetServiceASGIApplication(Greeter())
109+
```
110+
111+
=== "WSGI"
112+
113+
```python
114+
from greet.v1.greet_connect import GreetServiceSync, GreetServiceWSGIApplication
115+
from greet.v1.greet_pb2 import GreetResponse
116+
117+
class Greeter(GreetServiceSync):
118+
def greet(self, request, ctx):
119+
print("Request headers: ", ctx.request_headers())
120+
greeting = ""
121+
for message in request:
122+
greeting += f"Hello, {message.name}!\n"
123+
response = GreetResponse(greeting=f"Hello, {request.name}!")
124+
ctx.response_headers()["greet-version"] = "v1"
125+
return response
126+
127+
app = GreetServiceWSGIApplication(Greeter())
128+
```
129+
130+
That's it - metadata interceptors such as our [simple authentication interceptor](./interceptors.md#metadata-interceptors)
131+
can be used as-is with no other changes.

0 commit comments

Comments
 (0)