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