reverse proxy added

added tests for reverse proxy too
This commit is contained in:
Илья Глазунов
2025-12-03 00:05:11 +03:00
parent e2646a752a
commit 5262c5e1fb
7 changed files with 899 additions and 8 deletions
+81 -4
View File
@@ -2,6 +2,8 @@ import re
import mimetypes
from pathlib import Path
from typing import Dict, Any, Optional, Pattern
from urllib.parse import urlparse
import httpx
from starlette.requests import Request
from starlette.responses import Response, FileResponse, PlainTextResponse
from .logging_utils import get_logger
@@ -63,9 +65,10 @@ class Router:
class RequestHandler:
def __init__(self, router: Router, static_dir: str = "./static"):
def __init__(self, router: Router, static_dir: str = "./static", default_proxy_timeout: float = 30.0):
self.router = router
self.static_dir = Path(static_dir)
self.default_proxy_timeout = default_proxy_timeout
async def handle(self, request: Request) -> Response:
path = request.url.path
@@ -166,15 +169,89 @@ class RequestHandler:
async def _handle_proxy(self, request: Request, config: Dict[str, Any],
params: Dict[str, str]) -> Response:
# TODO: implement real proxying
proxy_url = config["proxy_pass"]
for key, value in params.items():
proxy_url = proxy_url.replace(f"{{{key}}}", value)
logger.info(f"Proxying request to: {proxy_url}")
parsed_proxy = urlparse(proxy_url)
return PlainTextResponse(f"Proxy to: {proxy_url}", status_code=200)
original_path = request.url.path
if parsed_proxy.path and parsed_proxy.path not in ("/", ""):
target_url = proxy_url
else:
base_url = f"{parsed_proxy.scheme}://{parsed_proxy.netloc}"
target_url = f"{base_url}{original_path}"
if request.url.query:
separator = "&" if "?" in target_url else "?"
target_url = f"{target_url}{separator}{request.url.query}"
logger.info(f"Proxying request to: {target_url}")
proxy_headers = dict(request.headers)
hop_by_hop_headers = [
"connection", "keep-alive", "proxy-authenticate",
"proxy-authorization", "te", "trailers", "transfer-encoding",
"upgrade", "host"
]
for header in hop_by_hop_headers:
proxy_headers.pop(header, None)
client_ip = request.client.host if request.client else "unknown"
proxy_headers["X-Forwarded-For"] = client_ip
proxy_headers["X-Forwarded-Proto"] = request.url.scheme
proxy_headers["X-Forwarded-Host"] = request.headers.get("host", "")
proxy_headers["X-Real-IP"] = client_ip
proxy_headers["Host"] = parsed_proxy.netloc
if "headers" in config:
for header in config["headers"]:
if ":" in header:
name, value = header.split(":", 1)
value = value.strip()
for key, param_value in params.items():
value = value.replace(f"{{{key}}}", param_value)
value = value.replace("$remote_addr", client_ip)
proxy_headers[name.strip()] = value
body = await request.body()
timeout = config.get("timeout", self.default_proxy_timeout)
try:
async with httpx.AsyncClient(timeout=timeout, follow_redirects=False) as client:
proxy_response = await client.request(
method=request.method,
url=target_url,
headers=proxy_headers,
content=body if body else None,
)
response_headers = dict(proxy_response.headers)
for header in hop_by_hop_headers:
response_headers.pop(header, None)
return Response(
content=proxy_response.content,
status_code=proxy_response.status_code,
headers=response_headers,
media_type=proxy_response.headers.get("content-type"),
)
except httpx.ConnectError as e:
logger.error(f"Proxy connection error to {target_url}: {e}")
return PlainTextResponse("502 Bad Gateway", status_code=502)
except httpx.TimeoutException as e:
logger.error(f"Proxy timeout to {target_url}: {e}")
return PlainTextResponse("504 Gateway Timeout", status_code=504)
except Exception as e:
logger.error(f"Proxy error to {target_url}: {e}")
return PlainTextResponse("502 Bad Gateway", status_code=502)
def create_router_from_config(regex_locations: Dict[str, Dict[str, Any]]) -> Router: