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:
Rodrigo Gomes da Silva authored and n committed 2026-09-22 12:06:09 +00:00
1 parent 3c5569a30e
commit 9a181c0026
5 files changed
+185 -17

No files matched your search

+3 -9
View File
@@ -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: