From 792e6206481bcba1e6bffbdd9a5e134687e0057b Mon Sep 17 00:00:00 2001 From: tan Date: Fri, 3 Jul 2026 07:55:31 +0530 Subject: [PATCH 1/2] Default the :http backend to HTTP/1.1 on HTTP.jl 2.x 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. --- src/client/httplibs/juliaweb_http.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/client/httplibs/juliaweb_http.jl b/src/client/httplibs/juliaweb_http.jl index 7db730b..a8e4975 100644 --- a/src/client/httplibs/juliaweb_http.jl +++ b/src/client/httplibs/juliaweb_http.jl @@ -73,6 +73,18 @@ end # `read_idle_timeout` (`readtimeout` still works but emits a deprecation warning). _http_read_timeout_kw(timeout) = _HTTP_V2 ? (; read_idle_timeout=timeout) : (; readtimeout=timeout) +# Transport protocol selection on HTTP.jl 2.x. 2.x defaults to `prefer_http2=true` +# and transparently upgrades any ALPN-capable TLS server to HTTP/2. We default to +# HTTP/1.1 (`:h1`) instead, because OpenAPI's streaming abort model — interrupt the +# read task and close the stream when the consumer closes the channel — assumes one +# request per connection, as in HTTP/1.1. Observed behavior over a reused HTTP/2 +# connection: after a few aborted watch/streaming cycles the shared connection's read +# loop wedges (the k8s watch hang), most likely from per-stream state left behind by +# the aborts. Callers who want the 2.x default can set `:http_protocol => :auto` +# (or `:h2`) in the client options. HTTP/1.x has no `protocol` keyword, so pass none. +_http_protocol_kw(ctx) = + _HTTP_V2 ? (; protocol=get(ctx.client.clntoptions, :http_protocol, :h1)) : (;) + function get_response_property(raw::HTTP.Response, name::Symbol) if name === :message return _http_statustext(raw) @@ -265,6 +277,7 @@ end function _http_request(ctx, method, url, headers, body, timeout, bytesread, captured_response, output) captured_response[] = http_response = HTTP.request(method, url, headers, body; _http_read_timeout_kw(timeout)..., + _http_protocol_kw(ctx)..., connect_timeout=timeout ÷ 2, retry=false, redirect=true, @@ -282,6 +295,7 @@ function _http_streaming_request(ctx, method, url, headers, body, timeout, bytes # HTTP.jl 2.0's `HTTP.open` does not accept a `verbose` keyword; only pass it on 1.x. open_kwargs = merge(_http_read_timeout_kw(timeout), + _http_protocol_kw(ctx), (; connect_timeout=timeout ÷ 2, retry=false, redirect=true, From e481cf4067ecbfaa224e9152ad8e9274398a90e7 Mon Sep 17 00:00:00 2001 From: tan Date: Fri, 3 Jul 2026 08:00:36 +0530 Subject: [PATCH 2/2] update patch version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3653fed..2cad120 100644 --- a/Project.toml +++ b/Project.toml @@ -4,7 +4,7 @@ keywords = ["Swagger", "OpenAPI", "REST"] license = "MIT" desc = "OpenAPI server and client helper for Julia" authors = ["JuliaHub Inc."] -version = "0.2.4" +version = "0.2.5" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"