peek.go 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. package util
  2. import (
  3. "bytes"
  4. "errors"
  5. "io"
  6. "strings"
  7. )
  8. // PeekedReadCloser is a ReadCloser that allows peeking into a stream and buffering it in memory.
  9. // It can be instantiated using the Peek function. After a stream has been peeked, it can still be fully
  10. // read by reading the PeekedReadCloser. It first drained from the memory buffer, and then from the remaining
  11. // underlying reader.
  12. type PeekedReadCloser struct {
  13. PeekedBytes []byte
  14. LimitReached bool
  15. peeked io.Reader
  16. underlying io.ReadCloser
  17. closed bool
  18. }
  19. // Peek reads the underlying ReadCloser into memory up until the limit and returns a PeekedReadCloser.
  20. // It does not return an error if limit is reached. Instead, LimitReached will be set to true.
  21. func Peek(underlying io.ReadCloser, limit int) (*PeekedReadCloser, error) {
  22. if underlying == nil {
  23. underlying = io.NopCloser(strings.NewReader(""))
  24. }
  25. peeked := make([]byte, limit)
  26. read, err := io.ReadFull(underlying, peeked)
  27. if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) && err != io.EOF {
  28. return nil, err
  29. }
  30. return &PeekedReadCloser{
  31. PeekedBytes: peeked[:read],
  32. LimitReached: read == limit,
  33. underlying: underlying,
  34. peeked: bytes.NewReader(peeked[:read]),
  35. closed: false,
  36. }, nil
  37. }
  38. // Read reads from the peeked bytes and then from the underlying stream
  39. func (r *PeekedReadCloser) Read(p []byte) (n int, err error) {
  40. if r.closed {
  41. return 0, io.EOF
  42. }
  43. n, err = r.peeked.Read(p)
  44. if errors.Is(err, io.EOF) {
  45. return r.underlying.Read(p)
  46. } else if err != nil {
  47. return 0, err
  48. }
  49. return
  50. }
  51. // Close closes the underlying stream
  52. func (r *PeekedReadCloser) Close() error {
  53. if r.closed {
  54. return io.EOF
  55. }
  56. r.closed = true
  57. return r.underlying.Close()
  58. }