About

This post documents how to stream a response body when making HTTP requests with the Go programming language and its standard library.

Background

The Go programming language provides a rich standard library that includes the net/http package for the implementation of HTTP clients and servers.

This post focuses on how to make HTTP requests with the net/http package, and in particular it focuses on how to stream a response body in chunks rather than reading the entire body into memory at once.

This can be useful in a number of scenarios including when a client wants to implement a progess bar for a large download, or when a client wants to eagerly process data as it arrives rather than wait to download the entire response body into memory at once.

It is also a pattern you could borrow if you find yourself implementing an interface that implements a stream of some sort and it is a pattern that is both common and idiomatic in Go.

Experiment

Context

Our experiment will implement a function that downloads the response body in chunks, and also prints the number of bytes downloaded so far at each iteration.

The http.Get method returns a http.Response object that represents a HTTP response, and among its fields are a Body field that returns a object that implements the io.ReadCloser interface.

When a request is made, the response body is not read into memory and instead it can be read into memory all at once using io.ReadAll or it can be read in smaller chunks using the Read method defined by the io.ReadCloser interface.

The response object also provides a ContentLength field that holds the total size of the response body in bytes but sometimes it might not be set, and in that case, its value will be -1. If you plan to implement a progress bar, this field is essential but in this experiment we won't need to use 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