Skip to content

Default the :http backend to HTTP/1.1 on HTTP.jl 2.x#100

Merged
tanmaykm merged 2 commits into
mainfrom
tan/http-force-h1
Jul 3, 2026
Merged

Default the :http backend to HTTP/1.1 on HTTP.jl 2.x#100
tanmaykm merged 2 commits into
mainfrom
tan/http-force-h1

Conversation

@tanmaykm

@tanmaykm tanmaykm commented Jul 3, 2026

Copy link
Copy Markdown
Member

Problem

HTTP.jl 2.x defaults to prefer_http2=true, and its :auto ALPN transparently upgrades any capable TLS server to HTTP/2 — callers (and generated clients like Kuber.jl) never ask for it.

The streaming-abort model added in #98 — interrupt the read task and close the stream when the consumer closes the channel — assumes one request per connection, which holds on HTTP/1.1. Over a reused, multiplexed HTTP/2 connection, each aborted watch/streaming cycle leaves per-stream state behind. After a few cycles the shared connection's read loop wedges.

Fix

Pin the transport to HTTP/1.1 (protocol=:h1) for both request paths (_http_request and _http_streaming_request), guarded by the existing _HTTP_V2 check (HTTP 1.x has no protocol keyword). HTTP.open forwards kwargs to the request layer, so it threads through the streaming path too.

The choice is overridable per client via a new :http_protocol client option (:auto or :h2) for callers who want the 2.x default:

_http_protocol_kw(ctx) =
    _HTTP_V2 ? (; protocol=get(ctx.client.clntoptions, :http_protocol, :h1)) : (;)

No change required in downstream callers — the :h1 default carries the fix.

Why default to h1 rather than scope it to streaming

The evidence directly implicates the streaming/abort path, but I default both paths to :h1: they share one connection pool, HTTP/1.1 is the conventional and well-tested transport. Callers who want HTTP/2 opt back in explicitly.

tanmaykm added 2 commits July 3, 2026 07:55
HTTP.jl 2.x defaults to prefer_http2=true and its :auto ALPN silently
upgrades any capable TLS server to HTTP/2. The streaming abort model
added in #98 (interrupt the read task + close the stream when the
consumer closes the channel) assumes one request per connection, as in
HTTP/1.1. Over a reused HTTP/2 connection each aborted watch/streaming
cycle leaves per-stream state behind, and after a few cycles the shared
connection read loop wedges (observed as a hung Kubernetes watch: the
socket in CLOSE-WAIT with the h2 read loop parked in read_frame!).

Pin the transport to HTTP/1.1 (protocol=:h1) for both the plain and
streaming request paths, guarded by the existing _HTTP_V2 check (1.x has
no protocol keyword). The choice is overridable per client via the
:http_protocol option (:auto or :h2) for callers who want the 2.x
default.
@tanmaykm tanmaykm merged commit 39c46f6 into main Jul 3, 2026
7 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.

1 participant