TeeReader is reader that writes to a writer, w, as you read from reader, r.

It is useful for situations where you need to read a reader multiple times. As you know, readers such as bytes.Buffer can be read one time only.

This is the official io.TeeReader definition:-

func TeeReader(r Reader, w Writer) Reader

This is what it really means

outReader := io.TeeReader(inReader, outWriter)

As you read from outReader, outWriter will fill up with the content that you’re reading.

Next, let’s take a look at a simple bytes.Buffer example, then we’ll see how we can use TeeReader to duplicate the content of a http response body.

Simple TeeReader Example

package main

import (
	"bytes"
	"fmt"
	"io"
)

func main() {
	inBuf := bytes.NewBufferString("Hello, 世界")
	dupBuf := new(bytes.Buffer)

	teeReader := io.TeeReader(inBuf, dubytes. Bufferead from teeReader
	// content from inBuf is duplicated to dupBuf as teeReader gets read
	content, err := io.ReadAll(teeReader)
	if err != nil {
		panic("failed to read teereader")
	}

	fmt.Printf("teereader content is: '%v'\n", string(content))
	fmt.Printf("dupBuf content is: '%v'\n", dupBuf.String())
}

Output

teereader content is: 'Hello, 世界'
dupBuf content is: 'Hello, 世界'

Using TeeReader to Read HTTP Response Body Multiple Times

One popular way to read a http response body multiple times is to first convert response body reader to []byte by calling io.ReadAll on it. This works but it’s considered less memory efficient.

For requests in the kilobytes range, the memory usage isn’t that different between io.ReadAll and TeeReader according to my benchmark.

But TeeReader does provide a cleaner interface and perhaps its code is more readable. Another way to do this is using io.MultiReader, and I’ll write about this another day.

Let’s get back to the example.

package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httptest"
)

func main() {
        // Create a test server
	svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello from Server")
	}))
	defer svr.Close()

	res, err := http.Get(svr.URL)
	if err != nil {
		log.Fatal(err)
	}

	dupBody := new(bytes.Buffer)
        r := io.TeeReader(res.Body, dupBody)

	// Again, the writer (dupBody) is written to as we read from teeReader
	greeting, err := io.ReadAll(teeReader)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("first: %s\n", greeting)

	// This is just to show that you can reset res.Body this way
	res.Body = io.NopCloser(dupBody)

	greeting, err = io.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("second: %s\n", greeting)
}

Output

first: Hello from Server
second: Hello from Server