A batteries included HTTP/1.1 client in OCaml

Close HTTP/2 connections after use to prevent Conpool reuse

When Conpool reuses a TCP connection for a second HTTP/2 request, the
code was attempting to send the HTTP/2 connection preface again, which
is invalid - you can only send the preface once per TCP connection.
This caused requests to hang.

The fix explicitly closes the flow after HTTP/2 request completion,
preventing Conpool from returning it to the idle pool. This is less
efficient than proper HTTP/2 multiplexing but ensures correctness.

Proper HTTP/2 connection reuse would require architectural changes to
tie H2_client state to TCP connections at the Conpool level.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+8 -2
+8 -2
lib/requests.ml
··· 513 513 if is_h2 then begin 514 514 (* Use HTTP/2 client via H2_adapter (handles caching and decompression) *) 515 515 Log.debug (fun m -> m "Using HTTP/2 for %s (ALPN negotiated)" url_to_fetch); 516 - match H2_adapter.request 516 + let result = H2_adapter.request 517 517 ~flow:conn_info.flow 518 518 ~uri:uri_to_fetch 519 519 ~headers:headers_with_cookies ··· 521 521 ~method_ 522 522 ~auto_decompress:t.auto_decompress 523 523 () 524 - with 524 + in 525 + (* Close the connection after HTTP/2 use to prevent Conpool from reusing it. 526 + HTTP/2 connection state (HPACK tables, flow control windows, stream IDs) 527 + is tied to the TCP connection, and we're not maintaining it across requests. 528 + Proper HTTP/2 multiplexing would require architectural changes to Conpool. *) 529 + Eio.Flow.close conn_info.flow; 530 + match result with 525 531 | Ok resp -> (resp.H2_adapter.status, resp.H2_adapter.headers, resp.H2_adapter.body) 526 532 | Error msg -> raise (Error.err (Error.Invalid_request { reason = "HTTP/2 error: " ^ msg })) 527 533 end else begin