Skip to content

Commit 9be2476

Browse files
Merge branch 'forward'
2 parents fe65294 + e76d4d2 commit 9be2476

15 files changed

+1259
-75
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ It's a fundation layer for building your own proxy-based tools.
1818
- **Certificate generation**: Dynamically generate and sign certificates for intercepted HTTPS traffic
1919
- **Request/Response interception**: Modify, log, or block HTTP(S) traffic in real-time
2020
- **Lightweight**: Pure Python implementation with only cryptography as a direct external dependency
21+
- **Optional implementation of forward proxy**: The implementation to forward HTTP request can be replace in order to use different HTTP client implementation
2122

2223
## Use Cases
2324

docs/advanced-usage.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Advanced Usage
2+
3+
This guide covers advanced usage of asyncio-https-proxy using the lower-level `HTTPSProxyHandler` for complete control over request handling and forwarding logic.
4+
5+
## When to Use HTTPSProxyHandler
6+
7+
Use the base `HTTPSProxyHandler` when you need:
8+
9+
- **Complete control** over request forwarding logic
10+
- **Custom HTTP client implementations** (using httpx, aiohttp, curl_cffi, ...)
11+
- **Fine-grained error handling** and retry logic
12+
13+
For most use cases, [HTTPSForwardProxyHandler](reference/https_forward_proxy_handler.md) with automatic forwarding is recommended. See the [Getting Started](getting-started.md) guide.
14+
15+
## HTTPSProxyHandler Overview
16+
17+
The `HTTPSProxyHandler` is the base class that provides:
18+
19+
- Request parsing and lifecycle hooks
20+
- Response writing utilities
21+
- Request body reading capabilities
22+
- **No automatic forwarding** - you implement all logic yourself
23+
24+
## Basic HTTPSProxyHandler Example
25+
26+
Here's a complete example using `HTTPSProxyHandler` with httpx for request forwarding:
27+
28+
```python title="base_usage.py"
29+
--8<-- "examples/base_usage.py"
30+
```
31+
32+
33+
## Handler Lifecycle Methods
34+
35+
The `HTTPSProxyHandler` provides these lifecycle hooks:
36+
37+
### `on_client_connected()`
38+
Called when a client connects and sends a request. This is where you implement your main request handling logic.
39+
40+
### `on_request_received()`
41+
Called after the request is fully parsed. Use for logging or request inspection.
42+
43+
### Helper Methods
44+
45+
- `self.read_request_body()` - Async generator for request body chunks
46+
- `self.write_response(data)` - Write response data to client
47+
- `self.flush_response()` - Flush response data to client
48+
49+
## Error Handling Best Practices
50+
51+
```python
52+
async def on_client_connected(self):
53+
try:
54+
await self._handle_request()
55+
except httpx.ConnectTimeout:
56+
await self._send_error(504, "Gateway Timeout")
57+
except httpx.ConnectError:
58+
await self._send_error(502, "Bad Gateway")
59+
except Exception as e:
60+
print(f"Unexpected error: {e}")
61+
await self._send_error(500, "Internal Server Error")
62+
63+
async def _send_error(self, code: int, message: str):
64+
"""Send standardized error response."""
65+
body = f"Proxy Error: {code} {message}".encode()
66+
response = (
67+
f"HTTP/1.1 {code} {message}\\r\\n"
68+
f"Content-Type: text/plain\\r\\n"
69+
f"Content-Length: {len(body)}\\r\\n"
70+
f"\\r\\n"
71+
).encode() + body
72+
73+
self.write_response(response)
74+
await self.flush_response()
75+
```

docs/getting-started.md

Lines changed: 101 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ asyncio-https-proxy is an embeddable, asyncio-based HTTPS forward proxy server t
1212
: The proxy can intercept HTTPS traffic by acting as a man-in-the-middle. It generates certificates on-the-fly using its own Certificate Authority (CA).
1313

1414
**Handler-Based Architecture**
15-
: You implement a custom handler class that extends [HTTPSProxyHandler](reference/https_proxy_handler.md) to define how requests and responses are processed.
15+
: You implement a custom handler class. For most use cases, extend [HTTPSForwardProxyHandler](reference/https_forward_proxy_handler.md) which provides automatic request forwarding. For advanced control, use the lower-level [HTTPSProxyHandler](advanced-usage.md).
1616

1717
**Asyncio Native**
1818
: Built using Python's asyncio framework for high-performance, non-blocking operations.
@@ -36,74 +36,63 @@ pip install asyncio-https-proxy
3636

3737
## Quick Start
3838

39-
Here's a complete working example that creates a basic HTTPS proxy server that use HTTPX to forward requests.
39+
Here's a complete working example that creates a basic HTTPS proxy server with automatic request forwarding and response analysis.
4040

4141
**Step 1: Create the Handler**
4242

43-
First, create a custom handler that extends [HTTPSProxyHandler](reference/https_proxy_handler.md):
43+
First, create a custom handler that extends [HTTPSForwardProxyHandler](reference/https_forward_proxy_handler.md):
4444

4545
```python
4646
import asyncio
47-
import httpx
48-
from asyncio_https_proxy import start_proxy_server, HTTPSProxyHandler, TLSStore
47+
from asyncio_https_proxy import start_proxy_server, HTTPSForwardProxyHandler, TLSStore
4948

5049

51-
class BasicProxyHandler(HTTPSProxyHandler):
52-
"""A basic proxy handler that forwards requests and logs activity."""
50+
class LoggingProxyHandler(HTTPSForwardProxyHandler):
51+
"""A proxy handler that automatically forwards requests and logs activity."""
52+
53+
def __init__(self):
54+
super().__init__()
55+
self.response_size = 0
5356

54-
async def on_client_connected(self):
55-
"""Called when a client connects to the proxy."""
56-
print(f"Client connected: {self.request.method} {self.request.url()}")
57-
5857
async def on_request_received(self):
5958
"""Called when a complete request has been received from the client."""
60-
# Log request headers
61-
print("Request Headers:")
59+
print(f"📤 Request: {self.request.method} {self.request.url()}")
60+
print(" Request Headers:")
6261
for key, value in self.request.headers:
63-
print(f" {key}: {value}")
62+
print(f" {key}: {value}")
63+
64+
# Reset response size counter
65+
self.response_size = 0
6466

65-
# Forward the request to the target server
66-
await self._forward_request()
67+
await self.forward_http_request()
68+
69+
async def on_response_received(self):
70+
"""Called when response headers are received from the upstream server."""
71+
if self.response:
72+
print(f"📥 Response: {self.response.status_code} {self.response.reason_phrase}")
73+
print(" Response Headers:")
74+
if self.response.headers:
75+
for key, value in self.response.headers:
76+
print(f" {key}: {value}")
6777

68-
async def _forward_request(self):
69-
"""Forward the request to the target server and relay the response."""
70-
# Create HTTP client for forwarding requests
71-
async with httpx.AsyncClient() as client:
72-
# Forward the request with all original headers and body
73-
async with client.stream(
74-
method=self.request.method,
75-
url=self.request.url(),
76-
headers=self.request.headers.to_dict(),
77-
content=self.read_request_body(), # Stream request body
78-
) as response:
79-
print(f"Response: {response.status_code} {response.reason_phrase}")
80-
81-
# Send response status line
82-
self.write_response(
83-
f"HTTP/1.1 {response.status_code} {response.reason_phrase}\\r\\n".encode()
84-
)
85-
86-
# Forward response headers
87-
for key, value in response.headers.items():
88-
self.write_response(f"{key}: {value}\\r\\n".encode())
89-
self.write_response(b"\\r\\n")
90-
91-
# Stream response body
92-
async for chunk in response.aiter_bytes():
93-
self.write_response(chunk)
78+
async def on_response_chunk(self, chunk: bytes) -> bytes:
79+
"""Called for each chunk of response data."""
80+
self.response_size += len(chunk)
81+
# Could modify content here if needed
82+
return chunk
83+
84+
async def on_response_complete(self):
85+
"""Called when response forwarding is complete."""
86+
print(f"✅ Response complete: {self.response_size} bytes")
87+
print("---")
9488
```
9589

96-
The handler is where you implement the custom logic by overriding the `on_` methods.
97-
98-
By default no forwarding capacity is provided you need to implement your own.
99-
This give you full control on the behaviors.
100-
90+
**Key Benefits of HTTPSForwardProxyHandler:**
10191

102-
```python
103-
self.write_response(chunk)
104-
```
105-
106-
Send back to the browser/HTTP client the response.
92+
- **Automatic Forwarding**: No need to implement HTTP client logic yourself
93+
- **Zero External Dependencies**: Uses only Python's built-in asyncio and ssl modules
94+
- **Streaming Support**: Efficiently handles large responses and chunked encoding
95+
- **Response Processing Hooks**: Intercept and analyze response content in real-time
10796

10897
**Step 2: Start the Proxy Server**
10998

@@ -118,32 +107,32 @@ async def main():
118107
port = 8888
119108

120109
print(f"Starting HTTPS proxy on {host}:{port}")
121-
print("\\nThe proxy will intercept both HTTP and HTTPS traffic.")
110+
print("\nThe proxy will intercept both HTTP and HTTPS traffic.")
122111
print("For HTTPS, it generates certificates on-the-fly using a built-in CA.")
123112

124113
# Initialize TLS store (creates CA certificate automatically)
125114
tls_store = TLSStore()
126-
print(f"\\nGenerated CA certificate. Clients may show security warnings.")
115+
print(f"\nGenerated CA certificate. Clients may show security warnings.")
127116
print("Use --insecure with curl")
128117

129118
# Start the proxy server
130119
server = await start_proxy_server(
131-
handler_builder=lambda: BasicProxyHandler(),
120+
handler_builder=lambda: LoggingProxyHandler(),
132121
host=host,
133122
port=port,
134123
tls_store=tls_store,
135124
)
136125

137-
print(f"\\nProxy server started. Test with:")
126+
print(f"\nProxy server started. Test with:")
138127
print(f" curl --insecure --proxy http://{host}:{port} https://httpbin.org/get")
139-
print("\\nPress Ctrl+C to stop...")
128+
print("\nPress Ctrl+C to stop...")
140129

141130
# Run the server
142131
async with server:
143132
try:
144133
await server.serve_forever()
145134
except KeyboardInterrupt:
146-
print("\\nShutting down proxy server...")
135+
print("\nShutting down proxy server...")
147136
finally:
148137
server.close()
149138
await server.wait_closed()
@@ -165,12 +154,17 @@ curl --proxy http://127.0.0.1:8888 http://httpbin.org/get
165154
Expected output shows the JSON response from httpbin.org, and your proxy will log:
166155

167156
```text
168-
Client connected: GET http://httpbin.org/get
169-
Request Headers:
170-
Host: httpbin.org
171-
User-Agent: curl/7.68.0
172-
Accept: */*
173-
Response: 200 OK
157+
📤 Request: GET http://httpbin.org/get
158+
Request Headers:
159+
Host: httpbin.org
160+
User-Agent: curl/7.68.0
161+
Accept: */*
162+
📥 Response: 200 OK
163+
Response Headers:
164+
Content-Type: application/json
165+
Content-Length: 312
166+
✅ Response complete: 312 bytes
167+
---
174168
```
175169

176170
**Test HTTPS Requests**
@@ -187,16 +181,48 @@ Configure your browser to use `127.0.0.1:8888` as an HTTP proxy. You'll need to
187181

188182
## Understanding the Handler Lifecycle
189183

190-
The `HTTPSProxyHandler` has a well-defined lifecycle:
184+
The `HTTPSForwardProxyHandler` has a well-defined lifecycle with automatic forwarding:
185+
186+
1. **Client Connection**: When a client connects, the request is automatically parsed
187+
2. **Request Processing**: `on_request_received()` is called, then automatic forwarding begins
188+
3. **Response Headers**: `on_response_received()` is called when response headers arrive
189+
4. **Response Body**: `on_response_chunk()` is called for each piece of response data
190+
5. **Response Complete**: `on_response_complete()` is called when forwarding finishes
191+
192+
This makes it much easier to get started compared to the lower-level [HTTPSProxyHandler](advanced-usage.md).
193+
194+
## Common Customizations
191195

192-
1. **Client Connection**: When a client connects, `on_client_connected()` is called
193-
2. **Request Parsing**: The server parses the HTTP request to [HTTPRequest](reference/http_request.md) and assigns it to `self.request`
194-
3. **Request Processing**: `on_request_received()` is called with the complete request
195-
4. **Response Generation**: Your handler processes the request and writes the response
196-
5. **Connection Cleanup**: The connection is automatically cleaned up
196+
### Content Analysis
197197

198-
See the [HTTPSProxyHandler](reference/https_proxy_handler.md) for more details on available methods and attributes.
199-
And [HTTPRequest](reference/http_request.md) for request structure.
198+
```python
199+
async def on_response_chunk(self, chunk: bytes) -> bytes:
200+
# Analyze content in real-time
201+
if b"error" in chunk.lower():
202+
print("⚠️ Error detected in response")
203+
return chunk
204+
```
205+
206+
### Response content Modification
207+
208+
```python
209+
async def on_response_chunk(self, chunk: bytes) -> bytes:
210+
# Replace text on the fly
211+
return chunk.replace(b"old_text", b"new_text")
212+
```
213+
214+
### Request Blocking
215+
216+
```python
217+
async def on_request_received(self):
218+
# Block requests to certain domains
219+
if "blocked.com" in self.request.host:
220+
self.write_response(b"HTTP/1.1 403 Forbidden\r\n\r\nBlocked")
221+
await self.flush_response()
222+
return # Don't forward the request
223+
224+
await self.forward_http_request()
225+
```
200226

201227
## Next Steps
202228

@@ -208,4 +234,6 @@ Now that you have a working proxy, you can:
208234
- Build web scraping or testing tools
209235
- Create security analysis tools
210236

211-
For more advanced usage, see the [API Reference](reference/index.md).
237+
For more advanced usage requiring complete control over request forwarding, see [Advanced Usage](advanced-usage.md).
238+
239+
For detailed API documentation, see the [API Reference](reference/index.md).

docs/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ It's a fundation layer for building your own proxy-based tools.
1818
- **Certificate generation**: Dynamically generate and sign certificates for intercepted HTTPS traffic
1919
- **Request/Response interception**: Modify, log, or block HTTP(S) traffic in real-time
2020
- **Lightweight**: Pure Python implementation with only cryptography as a direct external dependency
21+
- **Optional implementation of forward proxy**: The implementation to forward HTTP request can be replace in order to use different HTTP client implementation
22+
2123

2224
## Use Cases
2325

docs/reference/http_response.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# HTTPResponse
2+
3+
::: asyncio_https_proxy.HTTPResponse
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# HTTPSForwardProxyHandler
2+
3+
::: asyncio_https_proxy.HTTPSForwardProxyHandler

docs/reference/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ This section contains the complete API documentation for asyncio-https-proxy.
99
## Classes
1010

1111
- [HTTPSProxyHandler](https_proxy_handler.md) - Base handler class for processing requests
12+
- [HTTPSForwardProxyHandler](https_forward_proxy_handler.md) - Built-in forward proxy handler with automatic request forwarding
1213
- [HTTPRequest](http_request.md) - HTTP request representation
14+
- [HTTPResponse](http_response.md) - HTTP response representation
1315
- [HTTPHeader](http_header.md) - HTTP header collection
1416
- [TLSStore](tls_store.md) - TLS certificate management

examples/basic_usage.py renamed to examples/custom_http_client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
#!/usr/bin/env python3
22
"""
3-
Basic usage example for asyncio-https-proxy.
3+
Basic usage example for asyncio-https-proxy using HTTPX as
4+
the HTTP client to forward requests
45
56
This example shows how to set up a simple HTTPS proxy server.
67
"""
78

89
import asyncio
10+
911
import httpx
10-
from asyncio_https_proxy import start_proxy_server, HTTPSProxyHandler, TLSStore
12+
13+
from asyncio_https_proxy import HTTPSProxyHandler, TLSStore, start_proxy_server
1114

1215

1316
class BasicProxyHandler(HTTPSProxyHandler):

0 commit comments

Comments
 (0)