forked from aegis/pyserveX
reverse proxy added
added tests for reverse proxy too
This commit is contained in:
@@ -18,6 +18,7 @@ class ServerConfig:
|
||||
port: int = 8080
|
||||
backlog: int = 5
|
||||
default_root: bool = False
|
||||
proxy_timeout: float = 30.0
|
||||
redirect_instructions: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@@ -112,6 +113,7 @@ class Config:
|
||||
port=server_data.get('port', config.server.port),
|
||||
backlog=server_data.get('backlog', config.server.backlog),
|
||||
default_root=server_data.get('default_root', config.server.default_root),
|
||||
proxy_timeout=server_data.get('proxy_timeout', config.server.proxy_timeout),
|
||||
redirect_instructions=server_data.get('redirect_instructions', {})
|
||||
)
|
||||
|
||||
|
||||
@@ -33,9 +33,10 @@ class RoutingExtension(Extension):
|
||||
from .routing import create_router_from_config
|
||||
|
||||
regex_locations = config.get("regex_locations", {})
|
||||
default_proxy_timeout = config.get("default_proxy_timeout", 30.0)
|
||||
self.router = create_router_from_config(regex_locations)
|
||||
from .routing import RequestHandler
|
||||
self.handler = RequestHandler(self.router)
|
||||
self.handler = RequestHandler(self.router, default_proxy_timeout=default_proxy_timeout)
|
||||
|
||||
async def process_request(self, request: Request) -> Optional[Response]:
|
||||
try:
|
||||
|
||||
+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:
|
||||
|
||||
+5
-1
@@ -76,9 +76,13 @@ class PyServeServer:
|
||||
|
||||
def _load_extensions(self) -> None:
|
||||
for ext_config in self.config.extensions:
|
||||
config = ext_config.config.copy()
|
||||
if ext_config.type == "routing":
|
||||
config.setdefault("default_proxy_timeout", self.config.server.proxy_timeout)
|
||||
|
||||
self.extension_manager.load_extension(
|
||||
ext_config.type,
|
||||
ext_config.config
|
||||
config
|
||||
)
|
||||
|
||||
def _create_app(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user