io.Copy transfers bytes from an io.Reader to an io.Writer and returns the number of bytes written plus any transfer error. It reuses a 32 KB buffer, so a response can be processed without loading its complete contents into memory. section 7.3.4
Count an HTTP response without storing it
numberOfBytesDownloaded, err := io.Copy(io.Discard, response.Body) io.Discard receives and drops the downloaded bytes, while io.Copy returns the total transferred. This pattern is useful when only the response size matters. Close response.Body after processing it so the HTTP connection can be reused. section 7.3.4 section 7.3.5
Use io.Copy when:
- Streaming between files, network connections, response bodies, or in-memory buffers through the io.Reader and io.Writer interfaces. section 7.3.4
- Large input makes io.ReadAll unnecessarily memory-intensive; Copy keeps its transfer buffer bounded. section 7.3.4
- A custom buffer size is needed; use io.CopyBuffer to trade buffer memory against read and write operation overhead. section 7.3.4
- The source reaches the end normally; io.Copy consumes io.EOF and returns nil rather than reporting EOF as an error. section 7.3.5
func Copy(
dst io.Writer, #1
src io.Reader, #2
) (written int64, err error) #3
The signature makes the direction explicit: dst is the io.Writer, src is the io.Reader, and the result reports bytes written and an error. section 7.3.4
Listing 7.4 Processing a request and response (hit/hit.go)
package hit
import (
. . .
"io"
)
. . .
func Send(client *http.Client, req *http.Request) Result {
started := time.Now()
var (
bytes int64
code int
)
resp, err := client.Do(req) #1
if err == nil { // no error #2
defer resp.Body.Close() #3
code = resp.StatusCode
bytes, err = io.Copy( #4
io.Discard, #5
resp.Body, #6
)
}
return Result{
Duration: time.Since(started), #7
Bytes: bytes,
Status: code,
Error: err,
}
}
A complete HTTP helper calls io.Copy with io.Discard and resp.Body, records the byte count, handles the request error, and defers body closure. section 7.3.5

