fix(http): emit Connection: close exactly once and never crash on stubs
The first cut of the close advertisement had two defects, both caught by CI (9 red jobs) and by the deep-review gate's own MUST-FIX 1 wording. 1. Dedup was keyed off a private `_close_advertised` flag that end_headers() set itself. Every SSE endpoint already sends its own `Connection: close`, and BaseHTTPRequestHandler.send_header flips close_connection when it does — so end_headers() appended a SECOND copy to every stream response. Dedup now derives from the pending header buffer, which sees those site-level headers too. 2. `self.close_connection` was read unguarded. The attribute only exists once a request line has been parsed, so the many suite handlers built with `Handler.__new__(Handler)` raised AttributeError instead of emitting headers — and in _handle_write that AttributeError became a spurious 500. Both reads are now defensive, and check_auth_or_close() no longer invents the attribute on a handler that never had it (which would have left a bogus keep-alive verdict behind). The mechanism moves to api/helpers.advertise_connection_close(), which also puts server.py back under the original 750-line guard, so the test's ceiling bump to 760 is reverted rather than shipped.
This commit is contained in:
1 parent
3c5569a30e
commit
9a181c0026
5 files changed
+185
-17
No files matched your search
@@ -104,6 +104,7 @@ from api.auth import check_auth, check_auth_or_close, reset_trusted_auth_request
|
||||
from api.config import HOST, PORT, STATE_DIR, SESSION_DIR, DEFAULT_WORKSPACE
|
||||
from api.helpers import (
|
||||
j,
|
||||
advertise_connection_close,
|
||||
get_profile_cookie,
|
||||
_build_csp_report_only_policy,
|
||||
_CLIENT_DISCONNECT_ERRORS,
|
||||
@@ -332,12 +333,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
extra_frame_src = getattr(self, "_csp_extra_frame_src", None)
|
||||
self.send_header("Content-Security-Policy-Report-Only", self.csp_report_only_policy(extra_connect_src, extra_frame_src))
|
||||
self.send_header("Report-To", self._CSP_REPORT_TO)
|
||||
if self.close_connection and not getattr(self, "_close_advertised", False):
|
||||
# A reject-before-read leaves the body unread; the client must be
|
||||
# TOLD the connection dies or a pooled client reuses it and breaks.
|
||||
# BaseHTTPRequestHandler does not serialize the flag itself.
|
||||
self.send_header("Connection", "close")
|
||||
self._close_advertised = True
|
||||
advertise_connection_close(self) # tell the client when the socket dies
|
||||
super().end_headers()
|
||||
|
||||
def log_message(self, fmt, *args): pass # suppress default Apache-style log
|
||||
@@ -407,9 +403,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
set_request_profile(cookie_profile)
|
||||
try:
|
||||
parsed = urlparse(self.path)
|
||||
_is_csp_report_post = (
|
||||
parsed.path == "/api/csp-report" and self.command == "POST"
|
||||
)
|
||||
_is_csp_report_post = parsed.path == "/api/csp-report" and self.command == "POST"
|
||||
if not _is_csp_report_post and not check_auth_or_close(self, parsed): return
|
||||
result = route_func(self, parsed)
|
||||
if result is False:
|
||||
|
||||
Reference in new issue
Block a user