forked from aegis/pyserveX
reverse proxy added
added tests for reverse proxy too
This commit is contained in:
+81
-4
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user