Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5472943
update uv.lock
NEFORCEO Apr 26, 2026
7debe83
docs: update middleware.md
NEFORCEO Apr 26, 2026
c210dae
docs: update middleware.md
NEFORCEO Apr 26, 2026
aa43cfa
docs: update creating.md
NEFORCEO Apr 26, 2026
0feba24
docs: update examples.md
NEFORCEO Apr 26, 2026
664aeb0
docs: update docs/en/tutorial/middleware/index.md
NEFORCEO Apr 26, 2026
f3045c2
docs: update middleware.md
NEFORCEO Apr 26, 2026
7dbd821
docs: update middleware.md
NEFORCEO Apr 26, 2026
10b2bb3
docs: update creating.md
NEFORCEO Apr 26, 2026
c172234
docs: update examples.md
NEFORCEO Apr 26, 2026
4b46d43
docs: update docs/ru/tutorial/middleware/index.md
NEFORCEO Apr 26, 2026
252b038
update basic_middleware.py
NEFORCEO Apr 26, 2026
b09b576
update error_middleware.py
NEFORCEO Apr 26, 2026
899704d
update response_transformer.py
NEFORCEO Apr 26, 2026
c3f45f2
update fasthttp/__init__.py
NEFORCEO Apr 26, 2026
ea7c89c
update app.py
NEFORCEO Apr 26, 2026
8474bc5
update client.py
NEFORCEO Apr 26, 2026
887edd2
update middleware.py
NEFORCEO Apr 26, 2026
b5e4165
update types.py
NEFORCEO Apr 26, 2026
50dc50d
test: update conftest.py
NEFORCEO Apr 26, 2026
fee4edd
test: create test_middleware.py, test_routing.py and test_status.py
NEFORCEO Apr 26, 2026
6670cce
test: create test_logging.py
NEFORCEO Apr 26, 2026
9bbecf2
test: create test_meta.py
NEFORCEO Apr 26, 2026
1bbdf32
test: create test_helpers.py
NEFORCEO Apr 26, 2026
a659c9f
test: update test_exceptions.py
NEFORCEO Apr 26, 2026
619c5ba
test: update test_exceptions.py
NEFORCEO Apr 26, 2026
cfc0abc
test: update test_response.py
NEFORCEO Apr 26, 2026
22e1fd8
update release.yml and test_exceptions.py
NEFORCEO Apr 26, 2026
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
22 changes: 16 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ jobs:
echo "skip=false" >> $GITHUB_OUTPUT
fi

- name: Send Telegram announcement
if: steps.tag_check.outputs.skip == 'false'
run: |
TAG="v${{ env.version }}"
MESSAGE="⏳ *fasthttp-client ${TAG} coming soon...*

Новая версия уже собирается и скоро появится на PyPI 📦"

curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d "chat_id=${{ secrets.TELEGRAM_CHAT_ID }}" \
--data-urlencode "text=${MESSAGE}" \
-d "parse_mode=Markdown"

- name: Get current commit SHA
if: steps.tag_check.outputs.skip == 'false'
id: commit
Expand Down Expand Up @@ -93,14 +106,11 @@ jobs:
if: steps.tag_check.outputs.skip == 'false'
run: |
TAG="v${{ env.version }}"
MESSAGE="🚀 *Release Published!*

📦 *Package:* fasthttp-client ${TAG}
MESSAGE="🚀 *fasthttp-client ${TAG} released*

📝 *Description:* Fast and simple HTTP client library with async support and beautiful logging
⚡ Async HTTP client with middleware, routing & OpenAPI support

👤 *Author:* ${{ github.actor }}
🕐 *Time:* ${{ github.event.head_commit.timestamp }}"
👤 ${{ github.actor }} · 🕐 ${{ github.event.head_commit.timestamp }}"

KEYBOARD='{"inline_keyboard":[[{"text":"📦 PyPI","url":"https://pypi.org/project/fasthttp-client/"},{"text":"📖 Docs","url":"https://fasthttp.ndugram.dev"},{"text":"💻 GitHub","url":"https://github.com/'${GITHUB_REPOSITORY}'/releases/tag/'${TAG}'"}]]}'

Expand Down
246 changes: 107 additions & 139 deletions docs/en/middleware.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# Middleware

Middleware allows adding global logic that will be executed for all requests.
Middleware lets you intercept and modify every request and response through `FastHTTP` — without changing handler code.

## Introduction
## What middleware can do

Middleware in FastHTTP works similarly to middleware in FastAPI, but is designed for outgoing requests. It allows:
- Automatically add authorization headers
- Log all requests and responses
- Add timing and tracing headers
- Retry requests on specific response codes
- Transform response data

- Modifying requests before sending
- Modifying responses after receiving
- Handling errors
- Adding logging
- Adding authentication
## How it works

```
request → mw1.request → mw2.request → mw3.request → [HTTP]
response ← mw1.response ← mw2.response ← mw3.response ← [HTTP]
```

Middleware executes in `__priority__` order on the way in and in **reverse order** on the way out.

## Creating Middleware

Expand All @@ -23,44 +30,44 @@ from fasthttp.response import Response


class MyMiddleware(BaseMiddleware):
async def before_request(self, route, config):
"""Executed before each request."""
# Modify config
config.setdefault("headers", {})["X-Custom"] = "value"
return config

async def after_response(self, response, route, config):
"""Executed after each response."""
# Modify response
__return_type__ = None
__priority__ = 0
__methods__ = None
__enabled__ = True

async def request(self, method, url, kwargs):
kwargs["headers"] = kwargs.get("headers") or {}
kwargs["headers"]["X-Custom"] = "value"
return kwargs

async def response(self, response):
return response

async def on_error(self, error, route, config):
"""Executed on error."""
print(f"Error: {error}")
raise error
```

## Using Middleware
## Attaching to the app

```python
from fasthttp import FastHTTP
=== "List"

app = FastHTTP(middleware=MyMiddleware())
```
```python
app = FastHTTP(middleware=[AuthMiddleware(), LoggingMiddleware()])
```

### Multiple Middleware
=== "Pipe"

Execution order — first added executes first:
```python
app = FastHTTP(middleware=AuthMiddleware() | LoggingMiddleware())
```

```python
app = FastHTTP(middleware=[
AuthMiddleware(),
LoggingMiddleware(),
MetricsMiddleware(),
])
```
=== "Single"

```python
app = FastHTTP(middleware=MyMiddleware())
```

## Middleware Examples
## Examples

### Authentication

Expand All @@ -70,14 +77,18 @@ from fasthttp.middleware import BaseMiddleware


class AuthMiddleware(BaseMiddleware):
__return_type__ = bool
__priority__ = 0
__methods__ = None
__enabled__ = True

def __init__(self, token: str):
self.token = token

async def before_request(self, route, config):
headers = config.get("headers", {})
headers["Authorization"] = f"Bearer {self.token}"
config["headers"] = headers
return config
async def request(self, method, url, kwargs):
kwargs["headers"] = kwargs.get("headers") or {}
kwargs["headers"]["Authorization"] = f"Bearer {self.token}"
return kwargs


app = FastHTTP(middleware=[AuthMiddleware(token="your-token")])
Expand All @@ -92,12 +103,15 @@ from fasthttp.middleware import BaseMiddleware


class TraceMiddleware(BaseMiddleware):
async def before_request(self, route, config):
trace_id = str(uuid.uuid4())
headers = config.get("headers", {})
headers["X-Trace-ID"] = trace_id
config["headers"] = headers
return config
__return_type__ = None
__priority__ = 0
__methods__ = None
__enabled__ = True

async def request(self, method, url, kwargs):
kwargs["headers"] = kwargs.get("headers") or {}
kwargs["headers"]["X-Trace-ID"] = str(uuid.uuid4())
return kwargs


app = FastHTTP(middleware=[TraceMiddleware()])
Expand All @@ -107,20 +121,28 @@ app = FastHTTP(middleware=[TraceMiddleware()])

```python
import time
from contextvars import ContextVar
from fasthttp import FastHTTP
from fasthttp.middleware import BaseMiddleware


class LoggingMiddleware(BaseMiddleware):
async def before_request(self, route, config):
print(f"🚀 Sending: {route.method} {route.url}")
config["_start_time"] = time.time()
return config

async def after_response(self, response, route, config):
start_time = config.get("_start_time", 0)
duration = time.time() - start_time
print(f"✅ Response: {route.method} {route.url} - {response.status} ({duration:.2f}s)")
__return_type__ = None
__priority__ = 99
__methods__ = None
__enabled__ = True

def __init__(self) -> None:
self._start: ContextVar[float] = ContextVar("log_start", default=0.0)

async def request(self, method, url, kwargs):
print(f"→ {method} {url}")
self._start.set(time.monotonic())
return kwargs

async def response(self, response):
elapsed = time.monotonic() - self._start.get()
print(f"← {response.status} ({elapsed:.2f}s)")
return response


Expand All @@ -129,125 +151,71 @@ app = FastHTTP(middleware=[LoggingMiddleware()])

### Caching

FastHTTP comes with built-in CacheMiddleware:
FastHTTP comes with built-in `CacheMiddleware`:

```python
from fasthttp import FastHTTP, CacheMiddleware

app = FastHTTP(middleware=[
CacheMiddleware(ttl=3600, max_size=100) # TTL in seconds, max cache size
])
app = FastHTTP(
middleware=[CacheMiddleware(ttl=3600, max_size=100)]
)
```

Caches GET requests in memory.
Caches GET requests in memory with LRU eviction.

### Rate Limiting

```python
import time
from collections import defaultdict
from fasthttp import FastHTTP
from fasthttp.middleware import BaseMiddleware


class RateLimitMiddleware(BaseMiddleware):
def __init__(self, max_requests: int = 10, window: int = 60):
self.max_requests = max_requests
self.window = window
self.requests = defaultdict(list)

async def before_request(self, route, config):
now = time.time()
host = config.get("headers", {}).get("Host", "default")

# Clean old requests
self.requests[host] = [
t for t in self.requests[host]
if now - t < self.window
]

# Check limit
if len(self.requests[host]) >= self.max_requests:
raise Exception(f"Rate limit exceeded: {self.max_requests} requests per {self.window}s")

self.requests[host].append(now)
return config


app = FastHTTP(middleware=[RateLimitMiddleware(max_requests=10, window=60)])
```

### Response Modification
### Response modification

```python
from fasthttp import FastHTTP
from fasthttp.middleware import BaseMiddleware


class ResponseModifierMiddleware(BaseMiddleware):
async def after_response(self, response, route, config):
# Add headers to response
__return_type__ = None
__priority__ = 0
__methods__ = None
__enabled__ = True

async def response(self, response):
response.headers["X-Custom-Response"] = "value"
return response


app = FastHTTP(middleware=[ResponseModifierMiddleware()])
```

## Middleware Lifecycle

```
before_request → [Send Request] → after_response
or
on_error
```

### before_request(route, config)
## Class attributes

Called before sending each request. Can modify `config`.
| Attribute | Type | Description |
|-----------|------|-------------|
| `__return_type__` | `type \| None` | Type this middleware operates on |
| `__priority__` | `int` | Execution order — **lower runs first** |
| `__methods__` | `list[str] \| None` | HTTP methods to intercept. `None` = all methods |
| `__enabled__` | `bool` | `False` skips without removing from chain |

**Parameters:**
- `route` — route information
- `config` — request configuration
## Runtime toggle

**Returns:** modified `config`

### after_response(response, route, config)

Called after receiving a response. Can modify `response`.

**Parameters:**
- `response` — response object
- `route` — route information
- `config` — request configuration

**Returns:** modified `response`

### on_error(error, route, config)

Called when an error occurs.

**Parameters:**
- `error` — exception
- `route` — route information
- `config` — request configuration
```python
debug = LoggingMiddleware()
app = FastHTTP(middleware=[debug])

**Can:**
- Handle error and return a value
- Re-raise the error
debug.__enabled__ = False # disable
debug.__enabled__ = True # re-enable
```

## Comparison with Dependencies

| Feature | Middleware | Dependencies |
|---------|------------|--------------|
| Global application | ✅ Yes | ❌ No |
| Application to specific request | ❌ No | ✅ Yes |
| Specific request | ❌ No | ✅ Yes |
| Response modification | ✅ Yes | ❌ No |
| Error handling | ✅ Yes | ❌ No |
| Complexity | Higher | Lower |

## See Also
## See also

- [Creating Middleware](tutorial/middleware/creating.md) — full API, pipe chaining
- [Middleware Examples](tutorial/middleware/examples.md) — ready-made recipes
- [Middleware Reference](reference/middleware.md) — class documentation
- [Dependencies](dependencies.md) — for specific requests
- [Configuration](configuration.md) — settings
- [Quick Start](quick-start.md) — basics
Loading
Loading