Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for streaming responses #1087

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs_src/src/components/documentation/Navigation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ export const navigation = [
href: '/documentation/api_reference/exceptions',
title: 'Exceptions',
},
{
href: '/documentation/api_reference/streaming',
title: 'Streaming',
},
{
href: '/documentation/api_reference/scaling',
title: 'Scaling the Application',
Expand Down
282 changes: 282 additions & 0 deletions docs_src/src/pages/documentation/api_reference/streaming.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
export const description =
'Learn how to use streaming responses in Robyn for real-time data, large files, and server-sent events.'

## Overview

Like Batman's gadgets streaming from the Batcave to his utility belt, Robyn provides built-in support for streaming responses. This allows you to send data in chunks, perfect for large files, real-time updates, and server-sent events.

## Creating Streaming Responses

There are two ways to create streaming responses in Robyn:

### 1. Using the streaming parameter

<Row>
<CodeGroup title="Server">
```python
@app.get("/bat-signal", streaming=True)
async def stream_signal():
async def signal_generator():
while True:
yield b"Bat-Signal Active\n"
await asyncio.sleep(1)

return Response(
status_code=200,
headers={"Content-Type": "text/plain"},
description=signal_generator()
)
```
</CodeGroup>

<CodeGroup title="Client">
```bash
curl http://localhost:8000/bat-signal
```
</CodeGroup>
</Row>

### 2. Returning an Iterator/Generator

Robyn automatically detects iterators and generators and treats them as streaming responses:

<Row>
<CodeGroup title="Server">
```python
@app.get("/bat-signal")
async def stream_signal():
async def signal_generator():
while True:
yield b"Bat-Signal Active\n"
await asyncio.sleep(1)

return signal_generator() # Automatically detected as streaming
```
</CodeGroup>
</Row>

## Response Object

The Response class supports streaming through its constructor parameters:

```python
Response(
status_code=200,
headers={"Content-Type": "text/plain"},
description=generator(), # Can be str, bytes, or iterator/generator
streaming=True # Optional, automatically set for iterators/generators
)
```

### Parameters

| Name | Type | Description | Default |
|------|------|-------------|---------|
| status_code | int | Response status code | 200 |
| headers | Dict[str, str] | Response headers | None |
| description | Union[str, bytes, Iterator, AsyncIterator] | Content to stream | None |
| streaming | bool | Whether to treat as streaming response | False |

### Supported Types

Like Batman's versatile arsenal, the streaming response system supports multiple data types:

<Row>
<CodeGroup title="Binary">
```python
# Raw binary data (like Batcomputer logs)
async def generator():
yield b"Batcomputer Log Entry\n"
```
</CodeGroup>
<CodeGroup title="Text">
```python
# Text messages (like Alfred's updates)
async def generator():
yield "Master Wayne, your tea is ready\n".encode()
```
</CodeGroup>
<CodeGroup title="Numbers">
```python
# Numbers (like Batmobile telemetry)
async def generator():
yield str(speed).encode()
```
</CodeGroup>
<CodeGroup title="JSON">
```python
# JSON data (like Gotham City surveillance)
async def generator():
yield json.dumps({"location": "Crime Alley"}).encode()
```
</CodeGroup>
</Row>

## Server-Sent Events

For real-time updates from the Batcomputer:

<Row>
<CodeGroup title="Server">
```python
@app.get("/batcomputer/events", streaming=True)
async def batcomputer_feed():
async def event_generator():
while True:
data = {
"time": time.time(),
"alerts": get_gotham_alerts()
}
yield f"data: {json.dumps(data)}\n\n".encode()
await asyncio.sleep(1)

return Response(
status_code=200,
headers={
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
},
description=event_generator()
)
```
</CodeGroup>
<CodeGroup title="Client">
```javascript
const evtSource = new EventSource("/batcomputer/events");
evtSource.onmessage = (event) => {
console.log(JSON.parse(event.data));
};
```
</CodeGroup>
</Row>

## File Downloads

For streaming large files from the Batcomputer archives:

<Row>
<CodeGroup title="Server">
```python
@app.get("/batcomputer/files", streaming=True)
async def download_files():
async def file_generator():
chunk_size = 8192 # Size of a Batarang
with open("case_files.dat", "rb") as f:
while chunk := f.read(chunk_size):
yield chunk

return Response(
status_code=200,
headers={
"Content-Type": "application/octet-stream",
"Content-Disposition": "attachment; filename=evidence.dat"
},
description=file_generator()
)
```
</CodeGroup>
<CodeGroup title="Client">
```bash
curl -O http://localhost:8000/batcomputer/files
```
</CodeGroup>
</Row>

## Helper Functions

Robyn provides helper functions for common streaming scenarios:

```python
from robyn import html

@app.get("/bat-report", streaming=True)
async def stream_html():
async def generator():
yield "<html><body>"
for i in range(5):
yield f"<p>Bat-Signal sighting {i}</p>"
await asyncio.sleep(0.1)
yield "</body></html>"

return html(generator(), streaming=True)
```

## Common Headers

### Plain Text
```python
headers = {"Content-Type": "text/plain"}
```

### Server-Sent Events
```python
headers = {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
}
```

### File Downloads
```python
headers = {
"Content-Type": "application/octet-stream",
"Content-Disposition": "attachment; filename=file.dat"
}
```

## Error Handling

Even Batman needs contingency plans:

```python
async def generator():
try:
for item in evidence_items:
yield process(item)
except Exception as e:
yield f"Alert: Batcomputer Error - {str(e)}".encode()
return
```

## Testing

Test your streaming responses like Batman testing his equipment:

```python
@pytest.mark.asyncio
async def test_bat_signal():
async with aiohttp.ClientSession() as client:
async with client.get("http://localhost:8080/bat-signal") as response:
chunks = []
async for chunk in response.content:
chunks.append(chunk.decode())
assert len(chunks) > 0
```

## Best Practices

### Encode Data
Always encode strings to bytes (like encrypting Bat-communications)

### Chunk Size
Use appropriate chunk sizes (8KB-64KB for efficient data transfer)

### Resource Management
Clean up resources (like Batman cleaning up Gotham)

### Memory Usage
Don't accumulate data in memory (keep the Batcomputer running smoothly)

### Timeouts
Implement timeouts (even Batman needs sleep)

## What's Next?

Now that you've mastered streaming, you might want to explore:

- [WebSockets](/documentation/api_reference/websockets) - For real-time bidirectional communication
- [File Handling](/documentation/api_reference/file_handling) - For more file operations
- [Middleware](/documentation/api_reference/middleware) - For request/response processing

Loading
Loading