re

package module
v0.5.1 Latest Latest
Warning

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

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

README

re ♻️

Go Reference 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.

This 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.

A Note on Memory and Limits

Because re keeps the whole dataset in memory and replays it, it trades memory for re-readability. Don't reach for it with very large values or long-running streaming readers — only when the data is small and you reasonably expect to read it a second time.

To keep that tradeoff from becoming a liability, the HTTP body helpers take a maxBytes limit. A body larger than the limit returns an error instead of allocating without bound or silently parsing a truncated payload, so a hostile peer can't force a runaway allocation. Pass re.DefaultMaximum (1 MiB — comfortable for typical JSON payloads) when you have no stricter requirement of your own.

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. Each Read cycle ends with io.EOF and rewinds automatically, so the next consumer starts from the beginning.

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))
}

A re.Reader is also useful on its own, away from io.Reader plumbing:

  • re.NewReaderFromBytes(b) wraps a slice you already have (the slice is retained, not copied).
  • Reset() rewinds the cursor early, without draining to io.EOF first.
  • Bytes() and String() return the full contents directly, independent of the read cursor.

A Reader is not safe for concurrent use — the cursor is shared mutable state. To fan out to parallel consumers, give each its own reader over the same bytes with re.NewReaderFromBytes(r.Bytes()).

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 (up to maxBytes) then replace it with a fresh reader that can be read again by another process. They guard nil requests, responses, and bodies, so a nil body (legal on a HEAD response) yields an empty slice instead of a panic.

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

re.ReadResponseBody(response, maxBytes) does the same for a response. And re.CloneResponse(response, maxBytes) makes an independent copy of a response — reading and restoring the original's body so both the original and the clone remain readable.

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

View Source
const DefaultMaximum = int64(1 << 20)

DefaultMaximum is a sane default body-size limit (1 MiB) for callers that have no stricter requirement of their own. It is large enough for typical JSON API payloads while preventing a hostile peer from forcing an unbounded allocation.

Variables

This section is empty.

Functions

func CloneResponse added in v0.3.0

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

CloneResponse makes an exact copy of a response (reading up to maxBytes of its body) WITHOUT closing the original response body. A nil original yields a zero-value response. Pass DefaultMaximum if you have no stricter limit.

func ReadRequestBody added in v0.2.0

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

ReadRequestBody reads the request.Body (up to maxBytes) then replaces it with a new reader that can be read again by another process. If the body exceeds maxBytes, it returns an error and reads nothing further, so callers never parse a truncated body. Pass DefaultMaximum if you have no stricter limit.

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, re.DefaultMaximum)
	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, maxBytes int64) ([]byte, error)

ReadResponseBody reads the response.Body (up to maxBytes) then replaces it with a new reader that can be read again by another process. If the body exceeds maxBytes, it returns an error and reads nothing further, so callers never parse a truncated body. Pass DefaultMaximum if you have no stricter limit.

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