fix: avoid forcing frontend nginx upgrades (#4250)

* fix: avoid forcing frontend nginx upgrades

* fix: scope nginx upgrade assertion to frontend location
This commit is contained in:
Zhengcy05
2026-07-18 17:50:20 +08:00
committed by GitHub
parent 7757e38b7f
commit 5994fdf38c
3 changed files with 82 additions and 2 deletions
@@ -12,6 +12,36 @@ def _read(path: str) -> str:
return (REPO_ROOT / path).read_text(encoding="utf-8")
def _extract_nginx_location_block(content: str, location: str) -> str:
needle = f"location {location} {{"
start = content.find(needle)
assert start != -1, f"missing nginx {needle}"
brace_start = content.find("{", start)
assert brace_start != -1, f"missing nginx block opener for {needle}"
depth = 0
for index in range(brace_start, len(content)):
char = content[index]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return content[start : index + 1]
raise AssertionError(f"missing nginx block closer for {needle}")
def _assert_frontend_upgrade_header_is_conditional(content: str) -> None:
frontend_block = _extract_nginx_location_block(content, "/")
assert "map $http_upgrade $connection_upgrade" in content
assert "proxy_set_header Upgrade $http_upgrade;" in frontend_block
assert "proxy_set_header Connection $connection_upgrade;" in frontend_block
assert "proxy_set_header Connection 'upgrade';" not in frontend_block
def test_root_makefile_no_longer_exposes_transition_gateway_targets():
makefile = _read("Makefile")
@@ -104,6 +134,38 @@ def test_nginx_defers_cors_to_gateway_allowlist():
assert "if ($request_method = 'OPTIONS')" not in content
def test_nginx_frontend_upgrade_header_is_conditional():
for path in ("docker/nginx/nginx.local.conf", "docker/nginx/nginx.conf"):
content = _read(path)
_assert_frontend_upgrade_header_is_conditional(content)
def test_nginx_frontend_upgrade_check_allows_dedicated_websocket_locations():
content = """
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' '';
}
server {
location /api/threads/123/browser/stream {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
location / {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
}
"""
_assert_frontend_upgrade_header_is_conditional(content)
def test_gateway_cors_configuration_uses_gateway_allowlist():
gateway_config = _read("backend/app/gateway/config.py")
gateway_app = _read("backend/app/gateway/app.py")
+10 -1
View File
@@ -30,6 +30,15 @@ http {
"~*^https" https;
}
# Only mark frontend requests as connection upgrades when the browser
# actually requested one. Next.js dev HMR also uses long-lived HTTP
# streams, and forcing "Connection: upgrade" on those requests makes
# nginx treat the upstream response as an invalid upgrade handshake.
map $http_upgrade $connection_upgrade {
default upgrade;
'' '';
}
# ── Main server (path-based routing) ─────────────────────────────────
server {
listen 2026 default_server;
@@ -253,7 +262,7 @@ http {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $forwarded_proto;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Connection $connection_upgrade;
proxy_cache_bypass $http_upgrade;
# Timeouts
+10 -1
View File
@@ -27,6 +27,15 @@ http {
server 127.0.0.1:3000;
}
# Only mark frontend requests as connection upgrades when the browser
# actually requested one. Next.js dev HMR also uses long-lived HTTP
# streams, and forcing "Connection: upgrade" on those requests makes
# nginx treat the upstream response as an invalid upgrade handshake.
map $http_upgrade $connection_upgrade {
default upgrade;
'' '';
}
server {
listen 2026;
listen [::]:2026;
@@ -262,7 +271,7 @@ http {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Connection $connection_upgrade;
proxy_cache_bypass $http_upgrade;
# Disable response buffering for the frontend. Without this,