Skip to content

Commit daa37ec

Browse files
authored
Merge pull request #11 from filiksyos/cursor/investigate-http-422-error-df7c
Investigate http 422 error
2 parents 194251f + 2d79125 commit daa37ec

3 files changed

Lines changed: 81 additions & 5 deletions

File tree

src/server/main.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77

88
from dotenv import load_dotenv
99
from fastapi import FastAPI, Request, HTTPException
10-
from fastapi.responses import FileResponse, HTMLResponse
10+
from fastapi.exceptions import RequestValidationError
11+
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
1112
from fastapi.staticfiles import StaticFiles
1213
from fastapi.templating import Jinja2Templates
1314
from slowapi.errors import RateLimitExceeded
1415
from starlette.middleware.trustedhost import TrustedHostMiddleware
16+
from starlette.middleware.base import BaseHTTPMiddleware
1517

1618
from server.routers import index, dynamic
1719
from server.server_config import templates
@@ -28,6 +30,49 @@
2830
app.add_exception_handler(RateLimitExceeded, rate_limit_exception_handler)
2931

3032

33+
@app.exception_handler(RequestValidationError)
34+
async def validation_exception_handler(request: Request, exc: RequestValidationError):
35+
"""
36+
Handle validation errors (422) with detailed logging for debugging.
37+
38+
This helps diagnose environmental issues like missing form data or incorrect Content-Type headers.
39+
"""
40+
# Log the error details for debugging
41+
host = request.headers.get("host", "unknown")
42+
content_type = request.headers.get("content-type", "unknown")
43+
method = request.method
44+
path = request.url.path
45+
46+
error_details = {
47+
"host": host,
48+
"content_type": content_type,
49+
"method": method,
50+
"path": path,
51+
"errors": exc.errors(),
52+
}
53+
54+
print(f"ERROR: 422 Validation Error - Host: {host}, Content-Type: {content_type}, Path: {path}")
55+
print(f"ERROR: Validation details: {error_details}")
56+
57+
# If it's a form submission, return a helpful error message
58+
if method == "POST" and "form" in str(exc.errors()).lower():
59+
return JSONResponse(
60+
status_code=422,
61+
content={
62+
"detail": "Form validation failed. Make sure the request has Content-Type: application/x-www-form-urlencoded or multipart/form-data",
63+
"host": host,
64+
"content_type": content_type,
65+
"errors": exc.errors(),
66+
}
67+
)
68+
69+
# Otherwise return standard validation error
70+
return JSONResponse(
71+
status_code=422,
72+
content={"detail": exc.errors(), "body": exc.body}
73+
)
74+
75+
3176
# Mount static files dynamically to serve CSS, JS, and other static assets
3277
static_dir = Path(__file__).parent.parent / "static"
3378
app.mount("/static", StaticFiles(directory=static_dir), name="static")
@@ -37,6 +82,8 @@
3782
allowed_hosts = os.getenv("ALLOWED_HOSTS")
3883
if allowed_hosts:
3984
allowed_hosts = allowed_hosts.split(",")
85+
# Strip whitespace from each host
86+
allowed_hosts = [host.strip() for host in allowed_hosts]
4087
else:
4188
# Define the default allowed hosts for the application
4289
default_allowed_hosts = [
@@ -46,7 +93,36 @@
4693
]
4794
allowed_hosts = default_allowed_hosts
4895

96+
# Log allowed hosts for debugging (only in development)
97+
if os.getenv("DEBUG", "").lower() in ("true", "1"):
98+
print(f"DEBUG: Allowed hosts: {allowed_hosts}")
99+
100+
101+
class RequestLoggingMiddleware(BaseHTTPMiddleware):
102+
"""Middleware to log request details for debugging 422 errors."""
103+
104+
async def dispatch(self, request: Request, call_next):
105+
# Log POST requests that might fail validation
106+
if request.method == "POST":
107+
host = request.headers.get("host", "unknown")
108+
content_type = request.headers.get("content-type", "missing")
109+
path = request.url.path
110+
111+
# Only log if DEBUG is enabled or if content-type is suspicious
112+
if os.getenv("DEBUG", "").lower() in ("true", "1") or not content_type.startswith(("application/x-www-form-urlencoded", "multipart/form-data")):
113+
print(f"DEBUG POST: Host={host}, Content-Type={content_type}, Path={path}")
114+
115+
response = await call_next(request)
116+
return response
117+
118+
119+
# Add request logging middleware first (runs last in the chain)
120+
app.add_middleware(RequestLoggingMiddleware)
121+
49122
# Add middleware to enforce allowed hosts
123+
# Note: TrustedHostMiddleware can cause 400 errors if Host header doesn't match
124+
# Make sure ALLOWED_HOSTS environment variable includes your actual domain
125+
# If you're behind a proxy, you may need to configure X-Forwarded-Host handling
50126
app.add_middleware(TrustedHostMiddleware, allowed_hosts=allowed_hosts)
51127

52128

src/server/routers/dynamic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ async def process_catch_all(
6969
request: Request,
7070
input_text: str = Form(...),
7171
max_file_size: int = Form(...),
72-
pattern_type: str = Form(...),
73-
pattern: str = Form(...),
72+
pattern_type: str = Form("exclude"),
73+
pattern: str = Form(""),
7474
) -> HTMLResponse:
7575
"""
7676
Process the form submission with user input for query parameters.

src/server/routers/index.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ async def index_post(
4545
request: Request,
4646
input_text: str = Form(...),
4747
max_file_size: int = Form(...),
48-
pattern_type: str = Form(...),
49-
pattern: str = Form(...),
48+
pattern_type: str = Form("exclude"),
49+
pattern: str = Form(""),
5050
) -> HTMLResponse:
5151
"""
5252
Process the form submission with user input for query parameters.

0 commit comments

Comments
 (0)