Repository navigation
fix(proxy): negotiate the upstream HTTP protocol independently - #77
Conversation
HTTP/2 clients received 502 responses from HTTP/1.1-only TLS upstreams such as inn.gs. The client ALPN result selected a bare http2.Transport, which cannot fall back to HTTP/1.1. Use a shared http.Transport configured for upstream ALPN negotiation. Offer h2 and http/1.1, retain HTTP/2 when selected, and use HTTP/1.1 when the upstream selects it or no protocol. Host:port connection pools reuse the negotiated protocol across client tunnels without a probe handshake. Keep the HTTP/1.1 client path unchanged. Add regression coverage for HTTP/1.1-only and no-ALPN upstreams, HTTP/2, streaming and trailers, credential injection and logging, hop-by-hop headers, and per-host connection reuse. The new fallback tests failed before the fix. Existing gRPC coverage passes. Verified with go build ./..., go test -race ./..., go vet ./..., go run ./cmd/skill-lint, repeated focused HTTP/2 tests, and git diff --check.
📦 Previous Review (superseded by newer review)Claude finished @andybons's task in 20s —— View job Review of #77The approach is sound. Issues
Test gap: nothing exercises Not merging, as requested. |
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
TLS negotiation and cross-tunnel pooling of credential-bearing traffic warrant final human validation.
Review effort: Balanced
Findings: None
What changed in this PR
Decouples upstream protocol negotiation from client HTTP/2 negotiation in Gatekeeper’s TLS-intercepting proxy, allowing HTTP/2 clients to reach HTTP/1.1-only upstreams.
Changes:
- Adds a shared upstream transport with HTTP/2 negotiation, HTTP/1.1 fallback, connection reuse, and health checks.
- Preserves HTTP/1.1 client behavior.
- Adds regression coverage and an Unreleased changelog entry.
| File | Description |
|---|---|
| proxy/upstream.go | Configures the shared, protocol-negotiating upstream transport. |
| proxy/proxy.go | Uses the shared transport for HTTP/2 client tunnels. |
| proxy/http2_test.go | Tests fallback, streaming, trailers, credential handling, and cross-tunnel connection reuse. |
| CHANGELOG.md | Documents the upstream protocol fallback fix. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The shared ALPN transport captured upstream CAs on first use and retained idle connections after server shutdown. CA changes must affect new tunnels, as they did before pooling, and shutdown must release idle pooled connections. Invalidate the shared transport when CAs change, synchronize CA reads, and close idle connections from both server stop paths. Share the existing intercepted HTTP/1.1 transport configuration with the ALPN transport and use an RLock fast path for transport lookup. Preserve the existing per-tunnel trust semantics and safe stop-before-start behavior. Add red-first regressions for CA replacement and idle upstream cleanup in both server wrappers. Verify with go build ./..., go test -race ./..., go vet ./..., go run ./cmd/skill-lint, repeated focused tests, and git diff --check.
|
Addressed the review in the follow-up commit:
Full build, race-enabled suite, vet, skill lint, repeated focused regressions, and diff checks pass. Copilot reported no concrete findings; credential injection remains request-scoped, not connection-scoped. No merge performed. |
📦 Previous Review (superseded by newer review)Claude finished @andybons's task in 18s —— View job Review of PR #77I read the diff against What I checked
Non-blocking observations
I recommend merging once a human has confirmed the cross-tunnel pooling behaviour, as Copilot also suggested. I did not merge. |
Document the exported idle-pool cleanup method and the retained trust settings of existing tunnels after CA replacement. Active retired connections remain subject to the idle timeout; per-tunnel HTTP/1.1 shutdown behavior is unchanged. Verified with go build ./..., go test -race ./..., go vet ./..., go run ./cmd/skill-lint, and git diff --check.
|
Read the full follow-up review. Addressed observations 1 and 3 with concise comments documenting retired-tunnel trust/idle-timeout semantics and the exported Cross-tunnel reuse is covered by |
📦 Previous Review (superseded by newer review)Claude finished @andybons's task in 17s —— View job Review of #77 (re-review after follow-up commit)The earlier findings are addressed. I read the diff and did not run the tests.
No credential leak, race or deadlock found. Credentials are injected per request, and the pool is keyed by host:port only. Minor observations (non-blocking)
Not merging, as requested. |
Sharing the intercepted transport defaults imposed the HTTP/1.1 five-minute response-header timeout on h2 clients. The previous bare HTTP/2 transport had no deadline, so long-lived gRPC calls could regress. Keep the shared base configuration but explicitly disable the response-header timeout for the h2-capable transport, including fallback. Preserve the HTTP/1.1 client timeout and document the distinction in the changelog. A red-first regression reproduced the unintended 5m0s h2 timeout and now verifies no h2 deadline alongside the existing h1 timeout. Verified with go build ./..., go test -race ./..., go vet ./..., go run ./cmd/skill-lint, and git diff --check.
|
Addressed the new timeout observation: preserving long-lived h2/gRPC behavior is preferable to introducing an incidental five-minute deadline. The h2-capable transport now explicitly has no response-header timeout, as the previous bare h2 transport did; the h1 client path retains its existing five-minute timeout. The base settings remain shared. Added a red-first regression that failed with For the remaining observations: I retained the existing default per-host idle limit rather than add performance tuning to this correctness fix. Normal pool churn under bursts is distinct from the removed extra protocol-probe handshake; the host:port reuse regression still passes. Retired transports remain bounded by the existing 90-second idle timeout, as documented; there is no unbounded idle-connection leak or live trust-revocation change. Full build, race-enabled suite, vet, skill lint, and diff checks pass. Nothing was merged. |
📦 Previous Review (superseded by newer review)Claude finished @andybons's task in 17s —— View job Review of #77 (after the latest push)I found no blocking issues. I read the diff against Checked and fine
Non-blocking notes
The earlier feedback is addressed. Cross-tunnel pooling is covered by |
|
Thanks for the final re-review. Both remaining notes are unchanged, explicitly scoped behavior: h1 per-tunnel idle cleanup is pre-existing and remains outside this PR; active tunnels retain their original CA trust, as documented. No new changes are needed. All earlier findings are addressed, the cross-tunnel reuse regression passes, and the PR remains unmerged for human sign-off. |
|
Claude finished @andybons's task in 14s —— View job Review of #77 (re-review after the h2 response-header timeout change)I read the full diff against Checked
Non-blocking, already acknowledged
Not merging, as requested. A human should still confirm the cross-tunnel pooling behaviour. |
Problem
HTTP/2 box clients received 502 responses from HTTP/1.1-only TLS upstreams such as Inngest ingest (
inn.gs,POST /e/<key>). The proxy selected a barehttp2.Transportfrom the client ALPN result, but that transport cannot fall back to HTTP/1.1.Change
Use a shared
http.Transportconfigured withhttp2.ConfigureTransportsfor h2 clients. Upstream TLS offers h2 and http/1.1, uses h2 when selected, and falls back to HTTP/1.1 for an HTTP/1.1 or absent ALPN selection. Host:port connection pools reuse negotiated connections across client tunnels without a probe handshake. Keep HTTP/1.1 client behavior unchanged and retain HTTP/2 health checks. Add a v0.25.1 / Fixed changelog entry dated 2026-10-05.Verification
The new fallback regressions failed before the fix and pass afterward. Coverage includes h1-only and no-ALPN upstreams, h2, gRPC, streaming and trailers, hop-by-hop response headers, credential injection and logging, and per-host connection reuse across client tunnels. Independent review found no issues.
Passed:
go build ./...,go test -race ./...,go vet ./...,go run ./cmd/skill-lint, repeated focused HTTP/2 tests, andgit diff --check.Do not merge automatically.