About

This post shows how to stream an HTTP response body in Go using the standard library.

Background

Go’s standard library includes net/http, which gives you both an HTTP client and server. This post focuses on streaming a response body in chunks instead of reading everything into memory at once. That is useful for large downloads (where you want a progress bar) or when you want to process data as it arrives, and it is a common, idiomatic pattern for stream-like interfaces in Go.

Experiment

Overview

Our experiment implements a function that reads the response body in chunks and prints how many bytes have been downloaded so far. http.Get returns an http.Response. Its Body field is an io.ReadCloser we can read from and close when we are done.

By default, the body is streamed. You can pull it all at once with io.ReadAll, or read smaller chunks using Read. The response also has a ContentLength field with the total size in bytes, but it is sometimes -1 when the server does not send it. If you want a progress bar, you will need it, but this experiment does not rely on it:

package main

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

func main() {
  res, err := http.Get("https://www.openbsd.org/images/puffy78.gif")
  if err != nil {
    panic(err)
  }
  _, err = download(res.Body)
  if err != nil {
    panic(err)
  }
}

func download(body io.ReadCloser) ([]byte, error) {
  defer body.Close()
  chunk := make([]byte, 2048)
  buffer := make([]byte, 0, 2048*100)
  read := 0
  for {
    n, err := body.Read(chunk)
    if err == io.EOF {
      break
    } else if err != nil {
      return buffer, err
    } else {
      buffer = append(buffer, chunk[:n]...)
      read += n
    }
    fmt.Printf("\033[0K\rread %d bytes", read)
  }
  fmt.Println()
  return buffer, nil
}

Explanation

Conclusion

Play demo Demo of streaming a response body in Go