aiohttp Port Forwarding (Reverse Proxy)
Port forwarding with aiohttp allows you to create a proxy server that redirects incoming HTTP requests to another server port.
In this tutorial, you’ll learn how to implement port forwarding using aiohttp in Python.
Implement Basic Aiohttp Server
To start, you’ll need to create a basic aiohttp server. Here’s how you can do it:
import aiohttp from aiohttp import web import asyncio async def hello(request): return web.Response(text="Hello, World!") app = web.Application() app.router.add_get('/', hello) web.run_app(app)
Output:
======== Running on http://0.0.0.0:8080 ======== (Press CTRL+C to quit)
When you visit http://localhost:8080, you’ll see “Hello, World!” displayed.
Configure Port Forwarding
For example, let’s forward requests from port 8080 to port 9000.
We can do this by creating a reverse proxy. The aiohttp server will listen on port 8080 and forward requests to a service running on port 9000.
import aiohttp from aiohttp import web async def proxy_handler(request): async with aiohttp.ClientSession() as session: url = f"http://localhost:9000{request.path_qs}" method = request.method headers = request.headers.copy() data = await request.read() async with session.request(method, url, headers=headers, data=data) as resp: response = web.StreamResponse(status=resp.status, headers=resp.headers) await response.prepare(request) async for chunk in resp.content.iter_any(): await response.write(chunk) return response app = web.Application() app.router.add_route('*', '/{path:.*}', proxy_handler) if __name__ == '__main__': web.run_app(app, port=8080)
Now, let’s create a simple server that runs on port 9000 using aiohttp.
This server will respond to requests with a message indicating the path that was accessed.
from aiohttp import web async def handle(request): name = request.match_info.get('name', "Anonymous") text = f"Hello, {name}! You've reached the server on port 9000. Path: {request.path}" return web.Response(text=text) app = web.Application() app.router.add_get('/', handle) app.router.add_get('/{name}', handle) if __name__ == '__main__': web.run_app(app, port=9000)
This server will now be listening on port 9000. You can test it directly by visiting http://localhost:9000
in your web browser.
This setup will forward all requests received on port 8080 to the corresponding paths on port 9000.
When you visit HTTP://127.0.0.1:8080, you’ll get:
Hello, Anonymous! You've reached the server on port 9000. Path: /
Process and Modify Forwarded Requests
Add or modify headers
You can add or modify headers before forwarding the request:
async def proxy_handler(request): async with aiohttp.ClientSession() as session: url = f"http://localhost:9000{request.path_qs}" method = request.method headers = request.headers.copy() headers['X-Forwarded-For'] = request.remote headers['X-Forwarded-Proto'] = request.scheme headers['X-Forwarded-Host'] = request.host # Add custom header headers['X-Custom-Header'] = 'SomeValue' # Modify existing header if 'User-Agent' in headers: headers['User-Agent'] += ' via-proxy' data = await request.read() async with session.request(method, url, headers=headers, data=data) as resp: response = web.StreamResponse(status=resp.status, headers=resp.headers) await response.prepare(request) async for chunk in resp.content.iter_any(): await response.write(chunk) return response app = web.Application() app.router.add_route('*', '/{path:.*}', proxy_handler) if __name__ == '__main__': web.run_app(app, port=8080)
This example adds ‘X-Forwarded-For’ and ‘X-Forwarded-Host’ headers to the forwarded request.
Transform request body
To transform the request body, you can modify it before forwarding:
async def transform_body(body): try: data = json.loads(body) except json.JSONDecodeError: return body from datetime import datetime data['timestamp'] = datetime.now().isoformat() data['processed_by_proxy'] = True return json.dumps(data) async def proxy_handler(request): async with aiohttp.ClientSession() as session: url = f"http://localhost:9000{request.path_qs}" method = request.method headers = request.headers.copy() # Read and transform the body original_body = await request.read() transformed_body = await transform_body(original_body) async with session.request(method, url, headers=headers, data=transformed_body) as resp: response = web.StreamResponse(status=resp.status, headers=resp.headers) await response.prepare(request) async for chunk in resp.content.iter_any(): await response.write(chunk) return response app = web.Application() app.router.add_route('*', '/{path:.*}', proxy_handler) if __name__ == '__main__': web.run_app(app, port=8080)
The transform_body
function tries to parse the request body as JSON.
If successful, it adds a timestamp and a ‘processed’ flag to the data. If the body isn’t JSON, it returns the original body unchanged.
HTTPS Port Forwarding
To forward requests to HTTPS endpoints, you’ll need to handle SSL/TLS connections and deal with certificate verification:
import aiohttp import ssl from aiohttp import web import json import certifi async def transform_body(body): try: data = json.loads(body) from datetime import datetime data['timestamp'] = datetime.now().isoformat() data['processed_by_proxy'] = True return json.dumps(data) except json.JSONDecodeError: return body async def proxy_handler(request): ssl_context = ssl.create_default_context(cafile=certifi.where()) async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=ssl_context)) as session: url = f"https://httpbin.org{request.path}" method = request.method headers = request.headers.copy() original_body = await request.read() transformed_body = await transform_body(original_body) try: async with session.request(method, url, headers=headers, data=transformed_body) as resp: response = web.StreamResponse(status=resp.status, headers=resp.headers) await response.prepare(request) async for chunk in resp.content.iter_any(): await response.write(chunk) return response except aiohttp.ClientError as e: return web.Response(text=f"Error forwarding request: {str(e)}", status=500) app = web.Application() app.router.add_route('*', '/{path:.*}', proxy_handler) if __name__ == '__main__': web.run_app(app, port=8080)
This will forward requests to the HTTPBin API.
Mokhtar is the founder of LikeGeeks.com. He is a seasoned technologist and accomplished author, with expertise in Linux system administration and Python development. Since 2010, Mokhtar has built an impressive career, transitioning from system administration to Python development in 2015. His work spans large corporations to freelance clients around the globe. Alongside his technical work, Mokhtar has authored some insightful books in his field. Known for his innovative solutions, meticulous attention to detail, and high-quality work, Mokhtar continually seeks new challenges within the dynamic field of technology.