Better HTTP/1 connection reuse in Go 1.27

(go.dev)

116 points | by allddd 3 hours ago

9 comments

  • gandreani 30 minutes ago
    I find it kind of amusing to consider that the go team didn't commit to implement this automatic connection re-use until they probably encountered in the wild the horrible but correct third iteration of drainAndCloseBody.

    1. Suggest drainAndCloseBody // Team: this is reasonable

    2. Implement drainAndCloseBodyWithTimer // Team: no problems here

    3. then drainAsyncAndCloseBodyWithTimer // Team: a bit wordy but no problem here

    4. Someone at google uses 3. with an http/2 connection // Team: this is where we draw the line?

    I sympathize with the struggle of following golang's mission to be simple when you consider this common but tricky edge case. Iteration 3 makes sense but by gosh is it ugly ha.

  • jerf 1 hour ago
    Am I missing something or is none of this information documented in net/http itself? This is a surprising implementation of "Close" and should be documented somewhere.

    I can attest to the fact this is a Frequently Asked Question among people who are trying to Go as an HTTP client, so even though this feels somewhat icky, I can understand where it is coming from. However, I'm also missing, if I want to slam this connection closed, which is a reasonable thing to want, how do I do that without uselessly draining a socket I don't intend to reuse? Fake the "Connection: close" header into the Response object? Hope I can dig out an underlying socket of some sort under the concrete type of the Body.Response that I can bypass this code with? Seems like there should be a way to do that too.

    • jchw 1 hour ago
      I think it is reasonable for it to not be documented directly on Close. Close is arguably too high level for this: for example, on HTTP/2 this doesn't do what you would intend at all, since the request lifecycle is completely untethered from any underlying socket. If you really wanted to slam the connection closed ASAP it does actually seem more logical to grab the underlying connection and directly force it closed, even though this feels uglier since it requires knowing the underlying layers. To me this "Close" is logically from the perspective of the HTTP request itself and if I'm not getting myself confused and misunderstanding it doesn't really close the connection anyways (e.g. if there is no body and the connection is keep-alive'd, this wouldn't close the underlying connection, right?)
    • aleksi 1 hour ago
      https://pkg.go.dev/net/http#Client.Do

      > Note, however, that Transport will automatically try to read a Response Body to EOF asynchronously up to a conservative limit when a Body is closed.

      Similar wording appears in a few other places; search for “asynchronously”.

      • jerf 1 hour ago
        Oh, good. Not exactly where I expected it but it's there at least.
  • rkuska 1 hour ago
    Now if you use json’s Decoder you can read a body and still might end up without reused connection - if a response contains a newline.

    Decoder only reads X bytes (512? I don’t remember) and if the structure fits into these bytes exactly (and the multiplications of it) the newline leaves hanging there.

    I always check for Decoder.More (this drains whitespace) after decoding.

    • gandreani 37 minutes ago
      Oof, that is an edge case I wasn't aware of at all. Hard to know where it's documented since it's an integration of both those packages
  • nasretdinov 1 hour ago
    It sucks that this changes the behaviour under the hood that you previously coukd rely on the fact that closing the response body lets you skip downloading the HTTP body entirely, but I do like the tradeoff here, since it also limits the download time to just 50ms you'd not get overwhelmed if your network can't keep up with all the cancelled HTTP bodies, you'll just wait a little bit of traffic instead.
  • jamesponddotco 2 hours ago
    I can finally retire xhttp.DrainResponseBody[1], which I just learned, wasn’t doing that good of a job anyway.

    [1]: https://github.com/cipherdothost/x-go/blob/caa562ded82b8c417...

    • aleksi 1 hour ago
      I can finally remove a similar helper that I copied from project to project and from company to company since r60
      • jamesponddotco 1 hour ago
        I built that package exactly to stop copying code from project to project.

        Not because of the copy itself, since a little copy is better than a little dependency, as they say, but because it was a pain to update everything when I inevitably found a bug in what I copied.

  • donatj 1 hour ago
    > Using HTTP/1 (GODEBUG=http2client=0), calling this function 100 times against index.golang.org takes ~710 milliseconds on Go 1.26 on my machine. On Go 1.27, the same program takes ~150 milliseconds.

    Does this mean I need to disable http2 for this to work or is that just a further proof that http2 isn't actually being used?

    • giovannibajo1 1 hour ago
      As explained in the post, you don’t need any of this in HTTP/2, since you get the benefits of connection sharing without having to read the drain connection body by protocol design
      • MrDOS 1 hour ago
        Right. Although the article demonstrates the changes by making requests of a public web server, I would imagine this change has been primarily motivated by a desire to reduce overhead and latency on internal communication between microservices, which very often uses unencrypted HTTP/1 traffic.
    • 80fj0348i 1 hour ago
      HTTP/2 works differently, you can multiplex and prioritize different data over one TCP connection with HTTP/2. But 60-70% of the web is still HTTP/1 IIRC.
  • prussian 1 hour ago
    At the very end it mentions it won't drain bodies for Connection: close Does this work with reverse proxies? I've had issues with nginx still getting IO errors when my server doesn't consume bodies and bubbling them up as 5xx codes to the client. I believe I've seen this with Firefox as well, where XHR status codes would be 0. Early closing, without consuming body, seems to work as expected with Chrome and curl though.
  • seki285 52 minutes ago
    Any reason to be still using HTTP/1 in 2026?
    • nasretdinov 41 minutes ago
      If you don't need SSL you can't use HTTP/2 or HTTP/3. It doesn't make sense for externally facing services but sometimes is ok or even unavoidable for local networks
    • afavour 48 minutes ago
      You’re requesting from a third party backend you don’t control?
    • trgn 49 minutes ago
      imo, it's obvious for people writing ajax-y web apps, but backend devs need convincing.
  • mozidlb0e29352 1 hour ago
    [flagged]