why is golang http server failing with "broken pipe" when response exceeds 8kb?
Asked Answered
M

2

18

I have a example web server below where if you call curl localhost:3000 -v then ^C (cancel) it immediately (before 1 second), it will report write tcp 127.0.0.1:3000->127.0.0.1:XXXXX: write: broken pipe.

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    log.Fatal(http.ListenAndServe(":3000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            time.Sleep(1 * time.Second)

            // Why 8061 bytes? Because the response header on my computer
            // is 132 bytes, adding up the entire response to 8193 (1 byte 
            // over 8kb)
            if _, err := w.Write(make([]byte, 8061)); err != nil {
                    fmt.Println(err)
                    return
            }   
    })))
}

Based on my debugging, I have been able to conclude that this will only happen if the entire response is writing more than 8192 bytes (or 8kb). If my entire response write less than 8192, the broken pipe error is not returned.

My question is where is this 8192 bytes (or 8kb) buffer limit set? Is this a limit in Golang's HTTP write buffer? Is this related to the response being chunked? Is this only related to the curl client or the browser client? How can I change this limit so I can have a bigger buffer written before the connection is closed (for debugging purposes)?

Thanks!

Mixologist answered 3/4, 2017 at 16:6 Comment(0)
H
24

In net/http/server.go the output buffer is set to 4<<10, i.e. 4KB.

The reason you see the error at 8KB, is that it takes at least 2 writes to a socket to detect a closed remote connection. The first write succeeds, but the remote host sends an RST packet. The second write will be to a closed socket, which is what returns the broken pipe error.

Depending on the socket write buffer, and the connection latency, it's possible that even more writes could succeed before the first RST packet is registered.

Hearten answered 3/4, 2017 at 16:24 Comment(0)
L
-2

It is broken pipe, but you should use ioutil.ReadAll for small data size of response or io.copy for large data size of response.

  1. For ioutil.ReadAll
    defer response.Body.Close()
    body, err := ioutil.ReadAll(response.Body)
    if err != nil {
        logger.Errorf(ctx, "err is %+v", err)
        return nil, err
    }
  1. For io.copy
    // 10MB
    var wb = make([]byte, 0, 10485760)
    buf := bytes.NewBuffer(wb)
    written, err := io.Copy(buf, response.Body)
    body := wb[:written]
Levalloisian answered 6/12, 2022 at 14:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.