re

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 22, 2026 License: Apache-2.0 Imports: 4 Imported by: 10

README

re ♻️

GoDoc Version Build Status Go Report Card Codecov

Re-Reader for Go

In some circumstances, it's necessary to use a Golang io.Reader multiple times. While readers themselves don't support this, we can fake it by copying the value into a buffer and then replaying the values whenever asked.

WARNING: This library uses more resources than a regular reader. In particular, it's a bad idea to use this for very large values (because it keeps the whole dataset in memory) or long-running streaming readers (because it reads and replays the entire value). It should only be used when the data is small and you reasonably believe you will need to use the reader a second time.

However, it is useful for processing HTTP requests, particularly when multiple layers of an app all need the same access to the HTTP request body. This happens, for example, when an http signature middleware needs to validate the body digest before allowing the transaction to pass through to the main handler app.

Read from an io.Reader Multiple Times

Wrap any reader in a re.Reader. This copies all of its bytes into a memory buffer that can be re-read as many times as necessary.

func main() {
	// A single-use reader
	singleReader := strings.NewReader("Hello World.")

	// Wrap it so it can be read multiple times
	multipleReader, err := re.NewReader(singleReader)
	if err != nil {
		panic(err)
	}

	readAndPrint(multipleReader) // Hello World.
	readAndPrint(multipleReader) // Hello World.
	readAndPrint(multipleReader) // Hello World.
}

func readAndPrint(r io.Reader) {
	b, _ := io.ReadAll(r)
	fmt.Println(string(b))
}

Read the Body from an http.Request or http.Response

This library also includes helper functions to read from http.Request and http.Response bodies. These functions do not use a re.Reader, but simply read the existing Body reader then replace it with a fresh reader that can be read again by another process.

func handler(request *http.Request) {
	body, err := re.ReadRequestBody(request)
}

What matters here

  • Reader.Read advances a cursor and rewinds on EOF. Each Read returns the next bytes (n, nil) and signals 0, io.EOF only when the content is exhausted — at which point the cursor rewinds so the next read cycle starts over. This is what makes the same Reader re-readable. It also means re-reading depends on draining to EOF; to rewind early (mid-stream), call Reset(). A cursor-less implementation that returns EOF on every call silently truncates large inputs and repeats the first chunk — don't reintroduce one.

  • NewReader and NewReaderFromBytes return *Reader (a pointer). The cursor is mutable state, so the methods use a pointer receiver. Pass the *Reader around; copying the value would share the buffer but is not the intended use.

  • NewReaderFromBytes retains the slice, it does not copy it. Mutating the slice after the call mutates the Reader's contents. Pass a slice you own, or copy first.

  • The http.Body helpers replace the body with a fresh NopCloser. ReadRequestBody/ReadResponseBody read the body to completion and swap in a re-readable reader, so downstream handlers can read it again. They guard nil request/response and nil bodies (a nil body is legal, e.g. on a HEAD response) and return an empty slice rather than panicking.

  • CloneResponse returns (http.Response, error). It reads (and restores) the original's body to give the clone an independent copy; a failed read is returned rather than silently producing an empty clone.

Issues and Pull Requests Welcome

As is everything in life, re is a work in progress and will benefit from your experience reports, use cases, and contributions. If you have an idea for making this library better, send in a pull request. We're all in this together! ♻️

Documentation

Overview

Package re provides tools for reading an io.Reader more than once. It offers a re-readable Reader type that buffers content in memory, plus helpers for reading (and re-reading) HTTP request and response bodies.

Example (ReReader)

Example_reReader shows the core feature: a re.Reader can be read to completion more than once. After each read reaches EOF, the cursor rewinds so the next read starts again from the beginning.

package main

import (
	"fmt"
	"io"
	"strings"

	"github.com/benpate/re"
)

func main() {

	// A standard io.Reader can only be read once.
	singleReader := strings.NewReader("Hello World.")

	// Wrap it so it can be read repeatedly.
	multipleReader, err := re.NewReader(singleReader)
	if err != nil {
		panic(err)
	}

	for i := 0; i < 3; i++ {
		body, _ := io.ReadAll(multipleReader)
		fmt.Println(string(body))
	}

}
Output:
Hello World.
Hello World.
Hello World.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CloneResponse added in v0.3.0

func CloneResponse(original *http.Response) (http.Response, error)

CloneResponse makes an exact copy of a response WITHOUT closing the original response body. A nil original yields a zero-value response.

func ReadRequestBody added in v0.2.0

func ReadRequestBody(request *http.Request) ([]byte, error)

ReadRequestBody reads the request.Body then replaces it with a new reader that can be read again by another process.

Example

ExampleReadRequestBody shows that after reading a request body, the body is still readable by a later handler, because ReadRequestBody replaces it with a fresh reader.

package main

import (
	"fmt"
	"io"
	"net/http/httptest"
	"strings"

	"github.com/benpate/re"
)

func main() {

	request := httptest.NewRequest("POST", "https://example.com", strings.NewReader("request payload"))

	// One layer reads the body...
	body, _ := re.ReadRequestBody(request)
	fmt.Printf("first reader: %s\n", body)

	// ...and a later layer can still read the same body.
	again, _ := io.ReadAll(request.Body)
	fmt.Printf("second reader: %s\n", again)

}
Output:
first reader: request payload
second reader: request payload

func ReadResponseBody added in v0.2.0

func ReadResponseBody(response *http.Response) ([]byte, error)

ReadResponseBody reads the response.Body then replaces it with a new reader that can be read again by another process.

Types

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader is a simple, re-usable io.ReadCloser. It stores the entire contents of another io.Reader in memory, so it should not be used with large files.

Unlike a one-shot reader, a Reader can be read repeatedly: each Read advances an internal cursor and returns io.EOF when the content is exhausted, then the cursor rewinds so the next Read cycle starts again from the beginning. This lets one consumer read the content, then a later consumer read it again.

A Reader is NOT safe for concurrent use: the cursor is shared mutable state, so the re-reads must happen in sequence, not in parallel. To fan out to concurrent consumers, give each its own Reader over the same bytes (for example, NewReaderFromBytes(r.Bytes())).

func NewReader

func NewReader(input io.Reader) (*Reader, error)

NewReader creates a new Reader by reading the given io.Reader to completion.

func NewReaderFromBytes

func NewReaderFromBytes(buffer []byte) *Reader

NewReaderFromBytes creates a new Reader from the given slice of bytes. The slice is retained, not copied, so callers must not mutate it afterward.

func (*Reader) Bytes

func (r *Reader) Bytes() []byte

Bytes returns the Reader's full contents, independent of the read cursor.

func (*Reader) Close

func (r *Reader) Close() error

Close implements the io.Closer interface. It is a no-op (the content is held in memory) and never returns an error.

func (*Reader) Read

func (r *Reader) Read(p []byte) (int, error)

Read implements the io.Reader interface. It copies the next unread bytes into p, advancing the cursor. When the content is exhausted it returns 0, io.EOF and rewinds the cursor, so the Reader can be read again from the start.

Example

ExampleReader_Read shows that draining a Reader through a buffer smaller than its contents returns the full content, in order, across multiple Read calls.

package main

import (
	"fmt"
	"io"

	"github.com/benpate/re"
)

func main() {

	// Read through a small (4-byte) buffer until EOF. The buffer is allocated
	// once in the loop's init clause and reused across iterations.
	for reader, buffer := re.NewReaderFromBytes([]byte("Hello World.")), make([]byte, 4); ; {
		n, err := reader.Read(buffer)
		fmt.Printf("%q\n", buffer[:n])
		if err == io.EOF {
			break
		}
	}

}
Output:
"Hell"
"o Wo"
"rld."
""

func (*Reader) Reset added in v0.4.0

func (r *Reader) Reset()

Reset rewinds the cursor to the start of the content, so the next Read returns the content from the beginning.

Example

ExampleReader_Reset shows how Reset rewinds the cursor mid-stream, so a Reader that has only been partially consumed can be read again from the start.

package main

import (
	"fmt"
	"io"

	"github.com/benpate/re"
)

func main() {

	reader := re.NewReaderFromBytes([]byte("Hello World."))

	// Consume the first five bytes.
	first := make([]byte, 5)
	if _, err := reader.Read(first); err != nil {
		panic(err)
	}
	fmt.Printf("first read: %s\n", first)

	// Rewind, then read the whole thing.
	reader.Reset()
	all, _ := io.ReadAll(reader)
	fmt.Printf("after reset: %s\n", all)

}
Output:
first read: Hello
after reset: Hello World.

func (*Reader) String

func (r *Reader) String() string

String returns the Reader's full contents as a string, independent of the read cursor.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL