Skip to content

Commit 5a6144c

Browse files
committed
Add doc on streaming
Signed-off-by: Anuraag Agrawal <[email protected]>
1 parent 8bf7c8a commit 5a6144c

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed

docs/streaming.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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+
option go_package = "example/gen/greet/v1;greetv1";
77+
78+
message GreetRequest {
79+
string name = 1;
80+
}
81+
82+
message GreetResponse {
83+
string greeting = 1;
84+
}
85+
86+
service GreetService {
87+
rpc Greet(stream GreetRequest) returns (GreetResponse) {}
88+
}
89+
```
90+
91+
After running `buf generate` to update our generated code, we can amend our service implementation in
92+
`server.py`:
93+
94+
=== "ASGI"
95+
96+
```python
97+
from greet.v1.greet_connect import GreetService, GreetServiceASGIApplication
98+
from greet.v1.greet_pb2 import GreetResponse
99+
100+
class Greeter(GreetService):
101+
async def greet(self, request, ctx):
102+
print("Request headers: ", ctx.request_headers())
103+
greeting = ""
104+
async for message in request:
105+
greeting += f"Hello, {message.name}!\n"
106+
response = GreetResponse(greeting=greeting)
107+
ctx.response_headers()["greet-version"] = "v1"
108+
return response
109+
110+
app = GreetServiceASGIApplication(Greeter())
111+
```
112+
113+
=== "WSGI"
114+
115+
```python
116+
from greet.v1.greet_connect import GreetServiceSync, GreetServiceWSGIApplication
117+
from greet.v1.greet_pb2 import GreetResponse
118+
119+
class Greeter(GreetServiceSync):
120+
def greet(self, request, ctx):
121+
print("Request headers: ", ctx.request_headers())
122+
greeting = ""
123+
for message in request:
124+
greeting += f"Hello, {message.name}!\n"
125+
response = GreetResponse(greeting=f"Hello, {request.name}!")
126+
ctx.response_headers()["greet-version"] = "v1"
127+
return response
128+
129+
app = GreetServiceWSGIApplication(Greeter())
130+
```
131+
132+
That's it - metadata interceptors such as our [simple authentication interceptor](./interceptors.md#metadata-interceptors)
133+
can be used as-is with no other changes.

0 commit comments

Comments
 (0)