fastapi-reverse-proxy 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_reverse_proxy-0.2.0/src/fastapi_reverse_proxy.egg-info → fastapi_reverse_proxy-0.3.0}/PKG-INFO +15 -2
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/README.md +14 -1
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/pyproject.toml +1 -1
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy/__init__.py +2 -1
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy/proxy_pass.py +32 -10
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0/src/fastapi_reverse_proxy.egg-info}/PKG-INFO +15 -2
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/LICENSE +0 -0
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/setup.cfg +0 -0
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy/health_check.py +0 -0
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy/load_balance.py +0 -0
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy/proxy_httpx.py +0 -0
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy.egg-info/SOURCES.txt +0 -0
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy.egg-info/dependency_links.txt +0 -0
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy.egg-info/requires.txt +0 -0
- {fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-reverse-proxy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A robust, streaming-capable reverse proxy for FastAPI including WebSocket support.
|
|
5
5
|
Author-email: Tomás <tomas@suricatingss.xyz>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -36,6 +36,8 @@ A robust, streaming-capable reverse proxy for FastAPI/Starlette with built-in **
|
|
|
36
36
|
- **Unified Load Balancing**: Standard Round-Robin or Smart routing using a single utility.
|
|
37
37
|
- **Latency-Based Routing**: Automatically routes traffic to the fastest healthy server (HEAD probe).
|
|
38
38
|
- **Advanced Overrides**: Granular control over headers, body, and HTTP methods.
|
|
39
|
+
- **Smart Error Mapping**: Automatically converts upstream connection failures into standard HTTP 502 (Bad Gateway) and 504 (Gateway Timeout) responses.
|
|
40
|
+
- **Resilient Handshakes**: Customizable `open_timeout` for WebSockets to prevent proxy hangs during backend connection attempts.
|
|
39
41
|
- **Version Agnostic**: Automatically handles `websockets` library version differences (12.0+ vs Legacy).
|
|
40
42
|
|
|
41
43
|
## Quick Start
|
|
@@ -68,11 +70,20 @@ async def index(req: Request):
|
|
|
68
70
|
|
|
69
71
|
```
|
|
70
72
|
|
|
73
|
+
## 🛡️ Resilience & Error Handling
|
|
74
|
+
|
|
75
|
+
Error Handlingfastapi-reverse-proxy transforms upstream crashes into meaningful HTTPException responses (e.g., 502 Bad Gateway or 504 Gateway Timeout).
|
|
76
|
+
|
|
77
|
+
This allows you to implement custom failover logic, retry mechanisms, or specific error pages.
|
|
78
|
+
|
|
79
|
+
For a full implementation of a primary-to-backup failover system, see the [example](https://github.com/tfsantos05/fastapi-reverse-proxy/tree/main/examples/02_http_errors.py).
|
|
80
|
+
|
|
71
81
|
## Advanced Examples:
|
|
72
82
|
|
|
73
|
-
Check [
|
|
83
|
+
Check [examples](https://github.com/tfsantos05/fastapi-reverse-proxy/tree/main/examples) for full examples, including:
|
|
74
84
|
- **Websocket Proxy**
|
|
75
85
|
- **Socket.IO Proxy**
|
|
86
|
+
- **Error Handling & Failover** (`examples/error_handling_example.py`)
|
|
76
87
|
|
|
77
88
|
## Advanced Proxying
|
|
78
89
|
|
|
@@ -80,6 +91,7 @@ The `proxy_pass` function and `LoadBalancer.proxy_pass` provide deep customizati
|
|
|
80
91
|
|
|
81
92
|
| Parameter | Type | Description |
|
|
82
93
|
| :--- | :--- | :--- |
|
|
94
|
+
| `timeout` | `float` | Total request timeout in seconds (Default: `60.0`). |
|
|
83
95
|
| `method` | `str` | Force a specific HTTP method (e.g., `"POST"`). |
|
|
84
96
|
| `override_body` | `bytes` | Send custom data instead of the incoming request body. |
|
|
85
97
|
| `additional_headers` | `dict` | Append custom headers to the proxied request. |
|
|
@@ -115,6 +127,7 @@ The library implements "deferred negotiation" for WebSockets:
|
|
|
115
127
|
2. It establishes an upstream connection first.
|
|
116
128
|
3. Once the upstream accepts a protocol, the proxy calls `websocket.accept(subprotocol=...)` back to the client.
|
|
117
129
|
4. This ensures the entire tunnel (Client <-> Proxy <-> Upstream) uses the same negotiated protocol.
|
|
130
|
+
5. **Handshake Timeout**: Supports a customizable `timeout` parameter (default `10.0s`) to prevent hangs if the backend is unresponsive.
|
|
118
131
|
|
|
119
132
|
## Robustness & Safety
|
|
120
133
|
|
|
@@ -11,6 +11,8 @@ A robust, streaming-capable reverse proxy for FastAPI/Starlette with built-in **
|
|
|
11
11
|
- **Unified Load Balancing**: Standard Round-Robin or Smart routing using a single utility.
|
|
12
12
|
- **Latency-Based Routing**: Automatically routes traffic to the fastest healthy server (HEAD probe).
|
|
13
13
|
- **Advanced Overrides**: Granular control over headers, body, and HTTP methods.
|
|
14
|
+
- **Smart Error Mapping**: Automatically converts upstream connection failures into standard HTTP 502 (Bad Gateway) and 504 (Gateway Timeout) responses.
|
|
15
|
+
- **Resilient Handshakes**: Customizable `open_timeout` for WebSockets to prevent proxy hangs during backend connection attempts.
|
|
14
16
|
- **Version Agnostic**: Automatically handles `websockets` library version differences (12.0+ vs Legacy).
|
|
15
17
|
|
|
16
18
|
## Quick Start
|
|
@@ -43,11 +45,20 @@ async def index(req: Request):
|
|
|
43
45
|
|
|
44
46
|
```
|
|
45
47
|
|
|
48
|
+
## 🛡️ Resilience & Error Handling
|
|
49
|
+
|
|
50
|
+
Error Handlingfastapi-reverse-proxy transforms upstream crashes into meaningful HTTPException responses (e.g., 502 Bad Gateway or 504 Gateway Timeout).
|
|
51
|
+
|
|
52
|
+
This allows you to implement custom failover logic, retry mechanisms, or specific error pages.
|
|
53
|
+
|
|
54
|
+
For a full implementation of a primary-to-backup failover system, see the [example](https://github.com/tfsantos05/fastapi-reverse-proxy/tree/main/examples/02_http_errors.py).
|
|
55
|
+
|
|
46
56
|
## Advanced Examples:
|
|
47
57
|
|
|
48
|
-
Check [
|
|
58
|
+
Check [examples](https://github.com/tfsantos05/fastapi-reverse-proxy/tree/main/examples) for full examples, including:
|
|
49
59
|
- **Websocket Proxy**
|
|
50
60
|
- **Socket.IO Proxy**
|
|
61
|
+
- **Error Handling & Failover** (`examples/error_handling_example.py`)
|
|
51
62
|
|
|
52
63
|
## Advanced Proxying
|
|
53
64
|
|
|
@@ -55,6 +66,7 @@ The `proxy_pass` function and `LoadBalancer.proxy_pass` provide deep customizati
|
|
|
55
66
|
|
|
56
67
|
| Parameter | Type | Description |
|
|
57
68
|
| :--- | :--- | :--- |
|
|
69
|
+
| `timeout` | `float` | Total request timeout in seconds (Default: `60.0`). |
|
|
58
70
|
| `method` | `str` | Force a specific HTTP method (e.g., `"POST"`). |
|
|
59
71
|
| `override_body` | `bytes` | Send custom data instead of the incoming request body. |
|
|
60
72
|
| `additional_headers` | `dict` | Append custom headers to the proxied request. |
|
|
@@ -90,6 +102,7 @@ The library implements "deferred negotiation" for WebSockets:
|
|
|
90
102
|
2. It establishes an upstream connection first.
|
|
91
103
|
3. Once the upstream accepts a protocol, the proxy calls `websocket.accept(subprotocol=...)` back to the client.
|
|
92
104
|
4. This ensures the entire tunnel (Client <-> Proxy <-> Upstream) uses the same negotiated protocol.
|
|
105
|
+
5. **Handshake Timeout**: Supports a customizable `timeout` parameter (default `10.0s`) to prevent hangs if the backend is unresponsive.
|
|
93
106
|
|
|
94
107
|
## Robustness & Safety
|
|
95
108
|
|
{fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy/__init__.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from .proxy_pass import proxy_pass, proxy_pass_websocket
|
|
2
|
-
from .proxy_httpx import create_httpx_client, close_httpx_client, get_httpx_client
|
|
2
|
+
from .proxy_httpx import create_httpx_client, close_httpx_client, get_httpx_client, Proxy
|
|
3
3
|
from .load_balance import LoadBalancer
|
|
4
4
|
from .health_check import HealthChecker
|
|
5
5
|
|
|
@@ -9,6 +9,7 @@ __all__ = [
|
|
|
9
9
|
"create_httpx_client",
|
|
10
10
|
"close_httpx_client",
|
|
11
11
|
"get_httpx_client",
|
|
12
|
+
"Proxy",
|
|
12
13
|
"LoadBalancer",
|
|
13
14
|
"HealthChecker",
|
|
14
15
|
]
|
{fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy/proxy_pass.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from fastapi import Request, WebSocket, Response
|
|
1
|
+
from fastapi import Request, WebSocket, Response, HTTPException
|
|
2
2
|
from fastapi.responses import StreamingResponse
|
|
3
3
|
from starlette.background import BackgroundTask
|
|
4
4
|
from url_normalize import url_normalize
|
|
@@ -23,8 +23,18 @@ EXCLUDED_HEADERS = {
|
|
|
23
23
|
def url_normalize_ws(url:str):
|
|
24
24
|
u = urlparse(url)
|
|
25
25
|
return url_normalize(url.replace(u.scheme, "http", 1)).replace("http", u.scheme, 1)
|
|
26
|
-
|
|
27
26
|
|
|
27
|
+
async def handle_proxy_exception(e: Exception):
|
|
28
|
+
"""Maps internal exceptions to FastAPI HTTPExceptions for the client."""
|
|
29
|
+
if isinstance(e, httpx.ConnectTimeout):
|
|
30
|
+
raise HTTPException(status_code=504, detail="Gateway Timeout")
|
|
31
|
+
if isinstance(e, (httpx.ConnectError, httpx.RemoteProtocolError)):
|
|
32
|
+
raise HTTPException(status_code=502, detail="Bad Gateway")
|
|
33
|
+
if isinstance(e, httpx.ReadTimeout):
|
|
34
|
+
raise HTTPException(status_code=504, detail="Upstream Read Timeout")
|
|
35
|
+
# Re-raise other exceptions (like CancelledError or internal bugs) to avoid masking
|
|
36
|
+
raise e
|
|
37
|
+
|
|
28
38
|
async def proxy_pass(
|
|
29
39
|
request: Request,
|
|
30
40
|
host: str,
|
|
@@ -152,11 +162,14 @@ async def proxy_pass(
|
|
|
152
162
|
# Catch EVERY exception (including CancelledError) for local client cleanup
|
|
153
163
|
if not is_global_client and client:
|
|
154
164
|
await client.aclose()
|
|
155
|
-
|
|
165
|
+
|
|
166
|
+
# Transform httpx errors into proper HTTP responses
|
|
167
|
+
if not isinstance(e, asyncio.CancelledError):
|
|
168
|
+
await handle_proxy_exception(e)
|
|
169
|
+
|
|
156
170
|
raise e
|
|
157
171
|
|
|
158
172
|
|
|
159
|
-
|
|
160
173
|
async def proxy_pass_websocket(
|
|
161
174
|
websocket: WebSocket,
|
|
162
175
|
host: str,
|
|
@@ -164,13 +177,15 @@ async def proxy_pass_websocket(
|
|
|
164
177
|
subprotocols: Optional[list[str]] = None,
|
|
165
178
|
forward_query: bool = True,
|
|
166
179
|
additional_headers: Optional[dict] = None,
|
|
167
|
-
override_headers: Optional[dict] = None
|
|
180
|
+
override_headers: Optional[dict] = None,
|
|
181
|
+
timeout: float = 10.0
|
|
168
182
|
):
|
|
169
183
|
"""
|
|
170
184
|
Forwards incoming WebSocket connections to the target service.
|
|
171
185
|
- host: The host itself (without ending slash)
|
|
172
186
|
- path: The path with beggining slash (by default copies requests's path)
|
|
173
187
|
- forward_query: If True, automatically appends the request's query string.
|
|
188
|
+
- timeout: Time to wait for the connection handshake (open_timeout).
|
|
174
189
|
"""
|
|
175
190
|
|
|
176
191
|
if path is None: path = websocket.url.path
|
|
@@ -211,7 +226,8 @@ async def proxy_pass_websocket(
|
|
|
211
226
|
|
|
212
227
|
connect_kwargs = {
|
|
213
228
|
header_param: headers,
|
|
214
|
-
"subprotocols": supported_subprotocols
|
|
229
|
+
"subprotocols": supported_subprotocols,
|
|
230
|
+
"open_timeout": timeout
|
|
215
231
|
}
|
|
216
232
|
|
|
217
233
|
async with websockets.connect(url, **connect_kwargs) as target_ws:
|
|
@@ -221,7 +237,16 @@ async def proxy_pass_websocket(
|
|
|
221
237
|
|
|
222
238
|
except BaseException as e:
|
|
223
239
|
if not isinstance(e, asyncio.CancelledError):
|
|
224
|
-
|
|
240
|
+
# If the connection fails before accept(), we can raise a proper 502
|
|
241
|
+
if not websocket.client.connected: # Roughly checking if handshake finished
|
|
242
|
+
logger.error(f"WebSocket Connection Error: {e}")
|
|
243
|
+
# This is a bit tricky in WS, but if we haven't accepted yet, we can raise
|
|
244
|
+
try:
|
|
245
|
+
raise HTTPException(status_code=502, detail="Bad Gateway: WebSocket connection failed")
|
|
246
|
+
except RuntimeError: # If already accepted, we can't raise HTTPException
|
|
247
|
+
pass
|
|
248
|
+
else:
|
|
249
|
+
logger.error(f"WebSocket Proxy Error: {e}")
|
|
225
250
|
raise e
|
|
226
251
|
finally:
|
|
227
252
|
try:
|
|
@@ -230,9 +255,6 @@ async def proxy_pass_websocket(
|
|
|
230
255
|
pass
|
|
231
256
|
|
|
232
257
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
258
|
async def _handle_ws_bidirectional(websocket: WebSocket, target_ws):
|
|
237
259
|
"""Internal helper to manage bidirectional WS traffic with clean cancellation."""
|
|
238
260
|
async def client_to_target():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-reverse-proxy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A robust, streaming-capable reverse proxy for FastAPI including WebSocket support.
|
|
5
5
|
Author-email: Tomás <tomas@suricatingss.xyz>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -36,6 +36,8 @@ A robust, streaming-capable reverse proxy for FastAPI/Starlette with built-in **
|
|
|
36
36
|
- **Unified Load Balancing**: Standard Round-Robin or Smart routing using a single utility.
|
|
37
37
|
- **Latency-Based Routing**: Automatically routes traffic to the fastest healthy server (HEAD probe).
|
|
38
38
|
- **Advanced Overrides**: Granular control over headers, body, and HTTP methods.
|
|
39
|
+
- **Smart Error Mapping**: Automatically converts upstream connection failures into standard HTTP 502 (Bad Gateway) and 504 (Gateway Timeout) responses.
|
|
40
|
+
- **Resilient Handshakes**: Customizable `open_timeout` for WebSockets to prevent proxy hangs during backend connection attempts.
|
|
39
41
|
- **Version Agnostic**: Automatically handles `websockets` library version differences (12.0+ vs Legacy).
|
|
40
42
|
|
|
41
43
|
## Quick Start
|
|
@@ -68,11 +70,20 @@ async def index(req: Request):
|
|
|
68
70
|
|
|
69
71
|
```
|
|
70
72
|
|
|
73
|
+
## 🛡️ Resilience & Error Handling
|
|
74
|
+
|
|
75
|
+
Error Handlingfastapi-reverse-proxy transforms upstream crashes into meaningful HTTPException responses (e.g., 502 Bad Gateway or 504 Gateway Timeout).
|
|
76
|
+
|
|
77
|
+
This allows you to implement custom failover logic, retry mechanisms, or specific error pages.
|
|
78
|
+
|
|
79
|
+
For a full implementation of a primary-to-backup failover system, see the [example](https://github.com/tfsantos05/fastapi-reverse-proxy/tree/main/examples/02_http_errors.py).
|
|
80
|
+
|
|
71
81
|
## Advanced Examples:
|
|
72
82
|
|
|
73
|
-
Check [
|
|
83
|
+
Check [examples](https://github.com/tfsantos05/fastapi-reverse-proxy/tree/main/examples) for full examples, including:
|
|
74
84
|
- **Websocket Proxy**
|
|
75
85
|
- **Socket.IO Proxy**
|
|
86
|
+
- **Error Handling & Failover** (`examples/error_handling_example.py`)
|
|
76
87
|
|
|
77
88
|
## Advanced Proxying
|
|
78
89
|
|
|
@@ -80,6 +91,7 @@ The `proxy_pass` function and `LoadBalancer.proxy_pass` provide deep customizati
|
|
|
80
91
|
|
|
81
92
|
| Parameter | Type | Description |
|
|
82
93
|
| :--- | :--- | :--- |
|
|
94
|
+
| `timeout` | `float` | Total request timeout in seconds (Default: `60.0`). |
|
|
83
95
|
| `method` | `str` | Force a specific HTTP method (e.g., `"POST"`). |
|
|
84
96
|
| `override_body` | `bytes` | Send custom data instead of the incoming request body. |
|
|
85
97
|
| `additional_headers` | `dict` | Append custom headers to the proxied request. |
|
|
@@ -115,6 +127,7 @@ The library implements "deferred negotiation" for WebSockets:
|
|
|
115
127
|
2. It establishes an upstream connection first.
|
|
116
128
|
3. Once the upstream accepts a protocol, the proxy calls `websocket.accept(subprotocol=...)` back to the client.
|
|
117
129
|
4. This ensures the entire tunnel (Client <-> Proxy <-> Upstream) uses the same negotiated protocol.
|
|
130
|
+
5. **Handshake Timeout**: Supports a customizable `timeout` parameter (default `10.0s`) to prevent hangs if the backend is unresponsive.
|
|
118
131
|
|
|
119
132
|
## Robustness & Safety
|
|
120
133
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_reverse_proxy-0.2.0 → fastapi_reverse_proxy-0.3.0}/src/fastapi_reverse_proxy/proxy_httpx.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|