package router import ( "errors" "io" ) var ( _ Rereader = (*RereadableReadCloser)(nil) _ io.ReadCloser = (*RereadableReadCloser)(nil) ) // Rereader defines an interface for rewindable readers. type Rereader interface { Reread() } // RereadableReadCloser defines a wrapper around a [io.ReadCloser] reader // allowing to read the original reader multiple times. // // NB! Make sure to call Close after done working with the reader. type RereadableReadCloser struct { io.ReadCloser copy io.ReadWriteCloser closeErrors []error // MaxMemory specifies the max size of the in memory copy buffer // before switching to read/write from temp disk file. // // If negative or zero, defaults to [DefaultMaxMemory]. MaxMemory int64 } // Read implements the standard [io.Reader] interface. // // It reads up to len(p) bytes into p and at the same time copies // the read data into an internal buffer (memory + temp file). // // On EOF r is "rewinded" to allow reading multiple times. func (r *RereadableReadCloser) Read(p []byte) (int, error) { n, err := r.ReadCloser.Read(p) // copy the read bytes into the internal buffer if n > 0 { if r.copy == nil { r.copy = newBufferWithFile(r.MaxMemory) } if n, err := r.copy.Write(p[:n]); err != nil { return n, err } } // end reached -> reset for the next read if err == io.EOF { r.Reread() } return n, err } // Reread satisfies the [Rereader] interface and resets the r internal state to allow rereads. // // note: not named Reset to avoid conflicts with other reader interfaces. func (r *RereadableReadCloser) Reread() { if r.copy == nil { return // nothing to reset } // eagerly close the old reader to prevent accumulating too much memory or temp files if err := r.ReadCloser.Close(); err != nil { r.closeErrors = append(r.closeErrors, err) } r.ReadCloser = r.copy r.copy = nil } // Close implements the standard [io.Closer] interface by cleaning up related resources. // // It is safe to call Close multiple times. // Once Close is invoked the reader no longer can be used and should be discarded. func (r *RereadableReadCloser) Close() error { if r.copy != nil { if err := r.copy.Close(); err != nil { r.closeErrors = append(r.closeErrors, err) } } if err := r.ReadCloser.Close(); err != nil { r.closeErrors = append(r.closeErrors, err) } return errors.Join(r.closeErrors...) }