Skip to content

[KOB-52864] Fix proxy framing: strip hop-by-hop headers, recompute Content-Length - #62

Merged
chuong777 merged 1 commit into
kobiton:masterfrom
mimosa767:fix/kob-52864-proxy-framing
May 18, 2026
Merged

[KOB-52864] Fix proxy framing: strip hop-by-hop headers, recompute Content-Length#62
chuong777 merged 1 commit into
kobiton:masterfrom
mimosa767:fix/kob-52864-proxy-framing

Conversation

@mimosa767

@mimosa767 mimosa767 commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Generated Python pytest proxy was forwarding Connection: keep-alive without Content-Length or chunked framing, causing urllib3 to block indefinitely on response read after POST /session.
  • Fix in src/templates/python/proxy_server.py: strip hop-by-hop headers (Content-Length, Transfer-Encoding, Connection, Keep-Alive, Proxy-Connection, TE, Trailers, Upgrade) from the upstream response, set Content-Length from the actual (possibly-rewritten) body bytes, and force Connection: close.
  • Necessary because the response body can be rewritten in-flight (JSON Wire → W3C conversion for both /session and error responses), so the upstream Content-Length is no longer valid.

Linked issue

KOB-52864 — full root-cause analysis and pre/post-fix evidence is in the ticket comments.

Why this is correct

  • Hop-by-hop headers (RFC 7230 §6.1) must not be forwarded by an intermediary; the proxy was previously stripping only Content-Length.
  • Connection: close keeps framing unambiguous for a single-request-per-connection proxy and avoids keep-alive reuse hazards when upstream and downstream framings disagree.

Verification

  • Pre-fix (session 468671): driver hung immediately after POST /wd/hub/session 200; zero further Appium commands; Kobiton ended the session with state: TIMEOUT, endReason: USER-408, endMessage: \"Force end session by idling time out\" after 16m 12.7s.
  • Post-fix (session 468672): driver progressed cleanly through POST /appium/settings, POST /context, POST /timeouts, then 5+ recorded test commands (POST /execute/sync, POST /element, GET .../displayed, etc.) before hitting a separate XPath-locator issue tracked as KOB-52937. Clean DELETE /session 200 on teardown.
  • Patch was developed and verified against the generated bundle first; this PR ports the byte-identical patch back into the template.

Test plan

  • Regenerate a Python pytest bundle from this branch and run a smoke test against a Kobiton session
  • Confirm proxy logs show Content-Length set to the post-rewrite body length
  • Confirm Connection: close appears in forwarded response headers

🤖 Generated with Claude Code

…2864)

urllib3 was blocking on read because the proxy forwarded
Connection: keep-alive without Content-Length or chunked framing.
Fix: strip hop-by-hop headers, set Content-Length from actual body,
force Connection: close.

Verified with session 468672 — driver progresses through 5+ Appium
commands cleanly post-fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 18, 2026 00:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a hang in the generated Python pytest proxy where clients (urllib3) blocked indefinitely after POST /session because the proxy forwarded Connection: keep-alive from upstream without re-establishing valid framing after rewriting the response body (JSON Wire → W3C). The patch strips hop-by-hop headers, recomputes Content-Length from the actual (possibly rewritten) bytes, and forces Connection: close to keep framing unambiguous.

Changes:

  • Added a HOP_BY_HOP_HEADERS frozenset (per RFC 7230 §6.1) and filtered the forwarded response headers against it.
  • Set Content-Length based on the final response_body length and force Connection: close.
  • Reordered the response emission so headers are computed before send_response.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 182 to +183
response_headers = {key: val for key, val in response.headers.items()
if key.lower() != 'content-length'}
if key.lower() not in HOP_BY_HOP_HEADERS}
@chuong777
chuong777 merged commit 2e099d9 into kobiton:master May 18, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants