0

Please see the example here https://pkg.go.dev/net/http#example-Get. The snipped below as well:

func main() {
    res, err := http.Get("http://www.google.com/robots.txt")
    if err != nil {
        log.Fatal(err)
    }
    body, err := io.ReadAll(res.Body)
    res.Body.Close() // Why!?
    if res.StatusCode > 299 {
        log.Fatalf("Response failed with status code: %d and\nbody: %s\n", res.StatusCode, body)
    }
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s", body)
}

My question is: Why do I need to close the res.Body.Close() in line no 7. I did not open it. I see the explanation in What could happen if I don't close response.Body? and I understand that documentation says so.

Could there be a better way of solving this?

I think this is breaking the open/close principle. I think if ReadAll opened the steam it should close it. Is this usually how those things are done in Go?

demee
  • 582
  • 5
  • 18
  • 1
    The application opened the reader in the call to `http.Get()`. `Close()` releases resources allocated by`http.Get`. – Charlie Tumahai Oct 18 '21 at 23:09
  • 1
    `net/http` can't make a good decision about when to close the request body. It's being read straight from the tcp socket, not transferred through some sort of middleware. `res.Body.Close()` essentially tells `net/http` that you're done with the socket vis a vis this connection (that's not to say it actually closes the socket, which will be used for subsequent connections as appropriate) – erik258 Oct 18 '21 at 23:22
  • Voted to reopen, as I think my question is not about the consequences of not closing a stream, but the design decision to build it this way. Is this common in go? What is the best way of knowing and remembering when something needs to be closed ( yes, I know rtfm , but is there maybe a rule to learn and remember? ). – demee Oct 20 '21 at 22:57

3 Answers3

6

From the http.Client Do() docs:

... If the Body is not both read to EOF and closed, the Client's underlying RoundTripper (typically Transport) may not be able to re-use a persistent TCP connection to the server for a subsequent "keep-alive" request.

In short, if you don't follow this advice, you will accumulate "dead" http connections in your program, as requests are executed - but never cleaned up and/or reused. Your program's performance will degrade overtime - and depending on how many connections are generated - you may exhaust finite resources like file-descriptors that handle network connections.

colm.anseo
  • 19,337
  • 4
  • 43
  • 52
1

Have a look at Get func in http package,

func Get(url string) (resp *Response, err error) {
    return DefaultClient.Get(url)
}

it returns a pointer to Response which is a struct containing Body

type Response struct {
        . 
        .
        .
    Body io.ReadCloser
        .
        .
}

dig through the io package source code, you will find out that ReadCloser involves io.Reader and io.Writer. See this

Closing an open Reader would terminate a waiting channel and the underlying goroutine would be closed. Leaving too many of these open channels would block the host os to allocate free processes to the running program which is normally limited to consume a certain amount of processes (it could be altered to devour whole).

Davood Falahati
  • 1,474
  • 16
  • 34
1

The answer to all your questions basically is: Because the documentation says so. That's what documentation is good for. Some things might be "obvious" to you, some not. That's why you read the documentation and follow it.

Volker
  • 40,468
  • 7
  • 81
  • 87