From 00194156eaeff5cf2322f3eb3a41f2fc16881852 Mon Sep 17 00:00:00 2001 From: Erik Dubbelboer Date: Mon, 28 Nov 2022 11:48:18 +0100 Subject: [PATCH] Revert "feat: support mulit/range (#1398)" This reverts commit a468a7dd3734d9866ef6ab8ee1e36695f5c3b09c. --- fs.go | 787 ++++++++----------------------------------------- fs_test.go | 442 +++------------------------ header.go | 27 +- header_test.go | 2 +- 4 files changed, 172 insertions(+), 1086 deletions(-) diff --git a/fs.go b/fs.go index bb3ee07893..a8bcd13f9d 100644 --- a/fs.go +++ b/fs.go @@ -2,7 +2,6 @@ package fasthttp import ( "bytes" - "crypto/rand" "errors" "fmt" "html" @@ -502,8 +501,7 @@ type fsHandler struct { cacheGzip map[string]*fsFile cacheLock sync.Mutex - smallFileReaderPool sync.Pool - smallRangeReaderPool sync.Pool + smallFileReaderPool sync.Pool } type fsFile struct { @@ -522,9 +520,6 @@ type fsFile struct { bigFiles []*bigFileReader bigFilesLock sync.Mutex - - rangeBigFiles []*bigRangeReader - rangeBigFilesLock sync.Mutex } func (ff *fsFile) NewReader() (io.Reader, error) { @@ -545,8 +540,9 @@ func (ff *fsFile) smallFileReader() (io.Reader, error) { } r := v.(*fsSmallFileReader) r.ff = ff - if r.offset > 0 { - panic("BUG: fsSmallFileReader with non-nil offset found in the pool") + r.endPos = ff.contentLength + if r.startPos > 0 { + return nil, errors.New("bug: fsSmallFileReader with non-nil startPos found in the pool") } return r, nil } @@ -584,6 +580,7 @@ func (ff *fsFile) bigFileReader() (io.Reader, error) { return &bigFileReader{ f: f, ff: ff, + r: f, }, nil } @@ -610,175 +607,143 @@ func (ff *fsFile) decReadersCount() { ff.h.cacheLock.Unlock() } -func (ff *fsFile) NewRangeReader() (io.Reader, error) { - if ff.isBig() { - r, err := ff.bigRangeReader() - if err != nil { - ff.decReadersCount() - } - return r, err - } - return ff.smallRangeReader(), nil -} - -func (ff *fsFile) smallRangeReader() io.Reader { - v := ff.h.smallRangeReaderPool.Get() - if v == nil { - v = &smallRangeReader{ - sta: make([]int, 64), - end: make([]int, 64), - buf: make([]byte, 4096), - } - } - r := v.(*smallRangeReader) - r.ff = ff - r.sta, r.end, r.buf = r.sta[:0], r.end[:0], r.buf[:0] - r.hf = true - r.he = false - r.hasCurRangeBodyHeader = false - r.cRange = 0 - r.boundary = randomBoundary() - return r -} - -func (ff *fsFile) bigRangeReader() (io.Reader, error) { - if ff.f == nil { - panic("BUG: ff.f must be non-nil in bigRangeReader") - } - var r io.Reader - - ff.rangeBigFilesLock.Lock() - n := len(ff.rangeBigFiles) - if n > 0 { - r = ff.rangeBigFiles[n-1] - ff.rangeBigFiles = ff.rangeBigFiles[:n-1] - } - ff.rangeBigFilesLock.Unlock() - - if r != nil { - return r, nil - } - f, err := os.Open(ff.f.Name()) - if err != nil { - return nil, fmt.Errorf("cannot open already opened file: %s", err) - } - rr := &bigRangeReader{ - ff: ff, - f: f, - sta: make([]int, 64), - end: make([]int, 64), - buf: make([]byte, 4096), - hf: true, - he: false, - hasCurRangeBodyHeader: false, - cRange: 0, - boundary: randomBoundary(), - } - rr.sta, rr.end, rr.buf = rr.sta[:0], rr.end[:0], rr.buf[:0] - return rr, nil -} - // bigFileReader attempts to trigger sendfile // for sending big files over the wire. type bigFileReader struct { f *os.File ff *fsFile + r io.Reader + lr io.LimitedReader +} + +func (r *bigFileReader) UpdateByteRange(startPos, endPos int) error { + if _, err := r.f.Seek(int64(startPos), 0); err != nil { + return err + } + r.r = &r.lr + r.lr.R = r.f + r.lr.N = int64(endPos - startPos + 1) + return nil } func (r *bigFileReader) Read(p []byte) (int, error) { - return r.f.Read(p) + return r.r.Read(p) } func (r *bigFileReader) WriteTo(w io.Writer) (int64, error) { if rf, ok := w.(io.ReaderFrom); ok { // fast path. Send file must be triggered - return rf.ReadFrom(r.f) + return rf.ReadFrom(r.r) } // slow path - return copyZeroAlloc(w, r.f) + return copyZeroAlloc(w, r.r) } func (r *bigFileReader) Close() error { + r.r = r.f n, err := r.f.Seek(0, 0) if err == nil { - if n != 0 { - panic("BUG: File.Seek(0,0) returned (non-zero, nil)") + if n == 0 { + ff := r.ff + ff.bigFilesLock.Lock() + ff.bigFiles = append(ff.bigFiles, r) + ff.bigFilesLock.Unlock() + } else { + _ = r.f.Close() + err = errors.New("bug: File.Seek(0,0) returned (non-zero, nil)") } - ff := r.ff - ff.bigFilesLock.Lock() - ff.bigFiles = append(ff.bigFiles, r) - ff.bigFilesLock.Unlock() } else { - r.f.Close() + _ = r.f.Close() } r.ff.decReadersCount() return err } type fsSmallFileReader struct { - ff *fsFile - offset int64 + ff *fsFile + startPos int + endPos int } func (r *fsSmallFileReader) Close() error { ff := r.ff ff.decReadersCount() r.ff = nil - r.offset = 0 + r.startPos = 0 + r.endPos = 0 ff.h.smallFileReaderPool.Put(r) return nil } +func (r *fsSmallFileReader) UpdateByteRange(startPos, endPos int) error { + r.startPos = startPos + r.endPos = endPos + 1 + return nil +} + func (r *fsSmallFileReader) Read(p []byte) (int, error) { - ff := r.ff + tailLen := r.endPos - r.startPos + if tailLen <= 0 { + return 0, io.EOF + } + if len(p) > tailLen { + p = p[:tailLen] + } + ff := r.ff if ff.f != nil { - n, err := ff.f.ReadAt(p, r.offset) - r.offset += int64(n) + n, err := ff.f.ReadAt(p, int64(r.startPos)) + r.startPos += n return n, err } - if r.offset == int64(len(ff.dirIndex)) { - return 0, io.EOF - } - n := copy(p, ff.dirIndex[r.offset:]) - r.offset += int64(n) + + n := copy(p, ff.dirIndex[r.startPos:]) + r.startPos += n return n, nil } func (r *fsSmallFileReader) WriteTo(w io.Writer) (int64, error) { - if r.offset != 0 { - panic("BUG: no-zero offset! Read() mustn't be called before WriteTo()") - } ff := r.ff var n int var err error if ff.f == nil { - n, err = w.Write(ff.dirIndex) + n, err = w.Write(ff.dirIndex[r.startPos:r.endPos]) return int64(n), err } + if rf, ok := w.(io.ReaderFrom); ok { return rf.ReadFrom(r) } + + curPos := r.startPos bufv := copyBufPool.Get() buf := bufv.([]byte) for err == nil { - n, err = ff.f.ReadAt(buf, r.offset) + tailLen := r.endPos - curPos + if tailLen <= 0 { + break + } + if len(buf) > tailLen { + buf = buf[:tailLen] + } + n, err = ff.f.ReadAt(buf, int64(curPos)) nw, errw := w.Write(buf[:n]) - r.offset += int64(nw) + curPos += nw if errw == nil && nw != n { - panic("BUG: Write(p) returned (n, nil), where n != len(p)") + errw = errors.New("bug: Write(p) returned (n, nil), where n != len(p)") } if err == nil { err = errw } } copyBufPool.Put(bufv) + if err == io.EOF { err = nil } - return r.offset, err + return int64(curPos - r.startPos), err } func (h *fsHandler) cleanCache(pendingFiles []*fsFile) []*fsFile { @@ -951,14 +916,7 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) { return } - var r io.Reader - var err error - if h.acceptByteRange && len(byteRange) > 0 { - r, err = ff.NewRangeReader() - } else { - r, err = ff.NewReader() - } - + r, err := ff.NewReader() if err != nil { ctx.Logger().Printf("cannot obtain file reader for path=%q: %v", path, err) ctx.Error("Internal Server Error", StatusInternalServerError) @@ -979,27 +937,23 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) { if h.acceptByteRange { hdr.setNonSpecial(strAcceptRanges, strBytes) if len(byteRange) > 0 { - staList, endList, err := ParseByteRanges(byteRange, contentLength) + startPos, endPos, err := ParseByteRange(byteRange, contentLength) if err != nil { - r.(io.Closer).Close() - ctx.Logger().Printf("Cannot parse byte range %q for path=%q,error=%s", byteRange, path, err) + _ = r.(io.Closer).Close() + ctx.Logger().Printf("cannot parse byte range %q for path=%q: %v", byteRange, path, err) ctx.Error("Range Not Satisfiable", StatusRequestedRangeNotSatisfiable) return } - if err = r.(byteRangeHandler).ByteRangeUpdate(staList, endList); err != nil { - r.(io.Closer).Close() - ctx.Logger().Printf("Cannot seek byte range %q for path=%q, error=%s", byteRange, path, err) + if err = r.(byteRangeUpdater).UpdateByteRange(startPos, endPos); err != nil { + _ = r.(io.Closer).Close() + ctx.Logger().Printf("cannot seek byte range %q for path=%q: %v", byteRange, path, err) ctx.Error("Internal Server Error", StatusInternalServerError) return } - switch { - case len(staList) == 1: - hdr.SetContentRange(staList[0], endList[0], contentLength) - case len(staList) > 1: - hdr.SetContentType(fmt.Sprintf("multipart/byteranges; boundary=%s", r.(byteRangeHandler).Boundary())) - } - contentLength = r.(byteRangeHandler).ByteRangeLength() + + hdr.SetContentRange(startPos, endPos, contentLength) + contentLength = endPos - startPos + 1 statusCode = StatusPartialContent } } @@ -1026,63 +980,62 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) { ctx.SetStatusCode(statusCode) } +type byteRangeUpdater interface { + UpdateByteRange(startPos, endPos int) error +} + // ParseByteRange parses 'Range: bytes=...' header value. // // It follows https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 . -func ParseByteRanges(byteRange []byte, contentLength int) (startPos, endPos []int, err error) { +func ParseByteRange(byteRange []byte, contentLength int) (startPos, endPos int, err error) { b := byteRange if !bytes.HasPrefix(b, strBytes) { - return startPos, endPos, fmt.Errorf("unsupported range units: %q. Expecting %q", byteRange, strBytes) + return 0, 0, fmt.Errorf("unsupported range units: %q. Expecting %q", byteRange, strBytes) } + b = b[len(strBytes):] if len(b) == 0 || b[0] != '=' { - return startPos, endPos, fmt.Errorf("missing byte range in %q", byteRange) + return 0, 0, fmt.Errorf("missing byte range in %q", byteRange) } b = b[1:] - var n, sta, end, v int + n := bytes.IndexByte(b, '-') + if n < 0 { + return 0, 0, fmt.Errorf("missing the end position of byte range in %q", byteRange) + } - for _, ra := range bytes.Split(b, s2b(",")) { - n = bytes.IndexByte(ra, '-') - if n < 0 { - return startPos, endPos, fmt.Errorf("missing the end position of byte range in %q", byteRange) - } - if n == 0 { - v, err = ParseUint(ra[n+1:]) - if err != nil { - return startPos, endPos, err - } - sta = contentLength - v - if sta < 0 { - sta = 0 - } - startPos = append(startPos, sta) - endPos = append(endPos, contentLength-1) - continue - } - if sta, err = ParseUint(ra[:n]); err != nil { - return startPos, endPos, err - } - if sta >= contentLength { - return startPos, endPos, fmt.Errorf("the start position of byte range cannot exceed %d. byte range %q", contentLength-1, byteRange) - } - ra = ra[n+1:] - if len(ra) == 0 { - startPos = append(startPos, sta) - endPos = append(endPos, contentLength-1) - continue - } - if end, err = ParseUint(ra); err != nil { - return startPos, endPos, err - } - if end >= contentLength { - end = contentLength - 1 + if n == 0 { + v, err := ParseUint(b[n+1:]) + if err != nil { + return 0, 0, err } - if end < sta { - return startPos, endPos, fmt.Errorf("the start position of byte range cannot exceed the end position. byte range %q", byteRange) + startPos := contentLength - v + if startPos < 0 { + startPos = 0 } - startPos = append(startPos, sta) - endPos = append(endPos, end) + return startPos, contentLength - 1, nil + } + + if startPos, err = ParseUint(b[:n]); err != nil { + return 0, 0, err + } + if startPos >= contentLength { + return 0, 0, fmt.Errorf("the start position of byte range cannot exceed %d. byte range %q", contentLength-1, byteRange) + } + + b = b[n+1:] + if len(b) == 0 { + return startPos, contentLength - 1, nil + } + + if endPos, err = ParseUint(b); err != nil { + return 0, 0, err + } + if endPos >= contentLength { + endPos = contentLength - 1 + } + if endPos < startPos { + return 0, 0, fmt.Errorf("the start position of byte range cannot exceed the end position. byte range %q", byteRange) } return startPos, endPos, nil } @@ -1498,499 +1451,3 @@ func getFileLock(absPath string) *sync.Mutex { filelock := v.(*sync.Mutex) return filelock } - -type byteRangeHandler interface { - ByteRangeUpdate(sta, end []int) error - ByteRangeLength() int - Boundary() string - IsMultiRange() bool -} - -type smallRangeReader struct { - ff *fsFile - sta []int - end []int - - // handle multi range - buf []byte - hf bool - he bool - hasCurRangeBodyHeader bool - cRange int - boundary string -} - -func (r *smallRangeReader) ByteRangeUpdate(sta, end []int) error { - for i := range sta { - r.sta = append(r.sta, sta[i]) - r.end = append(r.end, end[i]+1) - } - return nil -} - -func (r *smallRangeReader) IsMultiRange() bool { - return len(r.sta) > 1 -} - -func (r *smallRangeReader) Boundary() string { - return r.boundary -} - -func (r *smallRangeReader) ByteRangeLength() int { - if !r.IsMultiRange() { - return r.end[0] - r.sta[0] - } - return multiRangeLength(r.sta, r.end, r.ff.contentLength, r.ff.contentType, r.boundary) -} - -func (r *smallRangeReader) Close() error { - ff := r.ff - ff.decReadersCount() - r.ff = nil - - r.sta, r.end, r.buf = r.sta[:0], r.end[:0], r.buf[:0] - r.cRange = 0 - r.hf = true - r.he = false - r.hasCurRangeBodyHeader = false - r.boundary = "" - ff.h.smallRangeReaderPool.Put(r) - return nil -} - -func (r *smallRangeReader) Read(p []byte) (int, error) { - ff := r.ff - var err error - cPos, cLen, n := 0, 0, 0 - - if r.cRange >= len(r.sta) && len(r.buf) == 0 && r.he { - return 0, io.EOF - } - - if r.IsMultiRange() { - cLen = len(p[cPos:]) - n = copy(p[cPos:], r.buf) - if len(r.buf) > cLen { - r.buf = r.buf[n:] - return n, nil - } - cPos += n - r.buf = r.buf[:0] - } - - for i := r.cRange; i < len(r.sta); i++ { - if r.sta[i] >= r.end[i] { - continue - } - - if r.IsMultiRange() && !r.hasCurRangeBodyHeader { - r.hasCurRangeBodyHeader = true - multiRangeBodyHeader(&r.buf, r.sta[i], r.end[i], r.ff.contentLength, r.ff.contentType, r.boundary, r.hf) - r.hf = false - cLen = len(p[cPos:]) - n = copy(p[cPos:], r.buf) - cPos += n - if len(r.buf) > cLen { - r.buf = r.buf[n:] - return cPos, nil - } - r.buf = r.buf[:0] - } - - cLen = len(p[cPos:]) - - // handle file - if ff.f != nil { - if r.end[i]-r.sta[i] > cLen { - n, err = ff.f.ReadAt(p[cPos:], int64(r.sta[i])) - r.sta[i] += n - return cPos + n, err - } - - // todo use pool - cBody := make([]byte, r.end[i]-r.sta[i]) - n, err = ff.f.ReadAt(cBody, int64(r.sta[i])) - if err != nil && err != io.EOF { - return cPos + n, err - } - n = copy(p[cPos:], cBody) - } else { - // handle dir - n = copy(p[cPos:], ff.dirIndex[r.sta[i]:r.end[i]]) - if r.end[i]-r.sta[i] > cLen { - r.sta[i] += n - return cPos + n, nil - } - } - - cPos += n - r.cRange = i + 1 - r.hasCurRangeBodyHeader = false - } - - if r.IsMultiRange() && !r.he { - multiRangeBodyEnd(&r.buf, r.boundary) - r.he = true - n = copy(p[cPos:], r.buf) - if len(r.buf) > len(p[cPos:]) { - r.buf = r.buf[n:] - return cPos + n, nil - } - cPos += n - r.buf = r.buf[:0] - } - return cPos, io.EOF -} - -func (r *smallRangeReader) WriteTo(w io.Writer) (int64, error) { - ff := r.ff - var err error - cPos, cLen, n, sum := 0, 0, 0, 0 - bufv := copyBufPool.Get() - buf := bufv.([]byte) - hf := true - if ff.f == nil { - for i := range r.sta { - - if r.IsMultiRange() { - buf = buf[:0] - if i > 0 { - hf = false - } - multiRangeBodyHeader(&buf, r.sta[i], r.end[i], ff.contentLength, ff.contentType, r.boundary, hf) - nw, errw := w.Write(buf) - if errw == nil && nw != len(buf) { - panic("BUG: buf returned(n, nil),where n != len(buf)") - } - sum += nw - if errw != nil { - return int64(sum), errw - } - } - - n, err = w.Write(ff.dirIndex[r.sta[i]:r.end[i]]) - sum += n - if err != nil { - return int64(sum), err - } - } - if r.IsMultiRange() { - buf = buf[:0] - multiRangeBodyEnd(&buf, r.boundary) - nw, errw := w.Write(buf) - if errw == nil && nw != len(buf) { - panic("BUG: buf returned (n, nil), where n != len(buf)") - } - sum += nw - err = errw - } - return int64(sum), err - } - - if rf, ok := w.(io.ReaderFrom); ok { - return rf.ReadFrom(r) - } - - for i := range r.sta { - - if r.IsMultiRange() { - buf = buf[:0] - if i > 0 { - hf = false - } - multiRangeBodyHeader(&buf, r.sta[i], r.end[i], ff.contentLength, ff.contentType, r.boundary, hf) - nw, errw := w.Write(buf) - if errw == nil && nw != len(buf) { - panic("BUG: buf returned(n, nil),where n != len(buf)") - } - sum += nw - if errw != nil { - return int64(sum), errw - } - } - - cPos = r.sta[i] - buf = buf[:4096] - for err == nil { - cLen = r.end[i] - cPos - if cLen <= 0 { - break - } - if len(buf) > cLen { - buf = buf[:cLen] - } - n, err = ff.f.ReadAt(buf, int64(cPos)) - nw, errw := w.Write(buf[:n]) - cPos += nw - sum += nw - if errw == nil && nw != n { - panic("BUG: Write(p) returned (n, nil), where n != len(p)") - } - if err == nil { - err = errw - } - } - } - if err == io.EOF { - err = nil - } - if r.IsMultiRange() { - buf = buf[:0] - multiRangeBodyEnd(&buf, r.boundary) - nw, errw := w.Write(buf) - if errw == nil && nw != len(buf) { - panic("BUG: buf returned (n, nil), where n != len(buf)") - } - sum += nw - if err == nil { - err = errw - } - } - return int64(sum), err -} - -type bigRangeReader struct { - ff *fsFile - f *os.File - sta []int - end []int - - // handle multi range - buf []byte - hf bool - he bool - hasCurRangeBodyHeader bool - cRange int - boundary string -} - -func (r *bigRangeReader) ByteRangeUpdate(sta, end []int) error { - for i := range sta { - r.sta = append(r.sta, sta[i]) - r.end = append(r.end, end[i]+1) - } - return nil -} - -func (r *bigRangeReader) IsMultiRange() bool { - return len(r.sta) > 1 -} - -func (r *bigRangeReader) Boundary() string { - return r.boundary -} - -func (r *bigRangeReader) ByteRangeLength() int { - if !r.IsMultiRange() { - return r.end[0] - r.sta[0] - } - return multiRangeLength(r.sta, r.end, r.ff.contentLength, r.ff.contentType, r.boundary) -} - -func (r *bigRangeReader) Close() error { - n, err := r.f.Seek(0, 0) - if err == nil { - if n != 0 { - panic("BUG: File.Seek(0,0) returned (non-zero, nil)") - } - ff := r.ff - ff.rangeBigFilesLock.Lock() - r.end, r.sta, r.buf = r.end[:0], r.sta[:0], r.buf[:0] - r.cRange = 0 - r.hf = true - r.he = false - r.hasCurRangeBodyHeader = false - - ff.rangeBigFiles = append(ff.rangeBigFiles, r) - ff.rangeBigFilesLock.Unlock() - } else { - r.f.Close() - } - r.ff.decReadersCount() - return err -} - -func (r *bigRangeReader) Read(p []byte) (int, error) { - if r.cRange >= len(r.sta) && len(r.buf) == 0 && r.he { - return 0, io.EOF - } - - ff := r.ff - var err error - cPos, cLen, n := 0, 0, 0 - - if r.IsMultiRange() { - cLen = len(p[cPos:]) - n = copy(p[cPos:], r.buf) - if len(r.buf) > cLen { - r.buf = r.buf[n:] - return n, nil - } - cPos += n - r.buf = r.buf[:0] - } - - for i := r.cRange; i < len(r.sta); i++ { - if r.sta[i] >= r.end[i] { - continue - } - - if r.IsMultiRange() && !r.hasCurRangeBodyHeader { - r.hasCurRangeBodyHeader = true - multiRangeBodyHeader(&r.buf, r.sta[i], r.end[i], r.ff.contentLength, r.ff.contentType, r.boundary, r.hf) - r.hf = false - cLen = len(p[cPos:]) - n = copy(p[cPos:], r.buf) - cPos += n - if len(r.buf) > cLen { - r.buf = r.buf[n:] - return cPos, nil - } - r.buf = r.buf[:0] - } - - cLen = len(p[cPos:]) - if r.end[i]-r.sta[i] > cLen { - n, err = r.f.ReadAt(p[cPos:], int64(r.sta[i])) - r.sta[i] += n - return cPos + n, err - } - - // todo use pool - cBody := make([]byte, r.end[i]-r.sta[i]) - n, err = ff.f.ReadAt(cBody, int64(r.sta[i])) - if err != nil && err != io.EOF { - return cPos + n, err - } - n = copy(p[cPos:], cBody) - cPos += n - r.cRange = i + 1 - r.hasCurRangeBodyHeader = false - } - - if r.IsMultiRange() && !r.he { - multiRangeBodyEnd(&r.buf, r.boundary) - r.he = true - n = copy(p[cPos:], r.buf) - if len(r.buf) > len(p[cPos:]) { - r.buf = r.buf[n:] - return cPos + n, nil - } - cPos += n - r.buf = r.buf[:0] - } - return cPos, io.EOF -} - -func (r *bigRangeReader) WriteTo(w io.Writer) (int64, error) { - if rf, ok := w.(io.ReaderFrom); ok { - return rf.ReadFrom(r) - } - - var err error - cPos, cLen, n, sum := 0, 0, 0, 0 - hf := true - - bufv := copyBufPool.Get() - buf := bufv.([]byte) - - for i := range r.sta { - - if r.IsMultiRange() { - buf = buf[:0] - if i > 0 { - hf = false - } - multiRangeBodyHeader(&buf, r.sta[i], r.end[i], r.ff.contentLength, r.ff.contentType, r.boundary, hf) - nw, errw := w.Write(buf) - if errw == nil && nw != len(buf) { - panic("BUG: buf returned(n, nil),where n != len(buf)") - } - sum += nw - if errw != nil { - return int64(sum), errw - } - } - - cPos = r.sta[i] - buf = buf[:4096] - for err == nil { - cLen = r.end[i] - cPos - if cLen <= 0 { - break - } - if len(buf) > cLen { - buf = buf[:cLen] - } - n, err = r.f.ReadAt(buf, int64(cPos)) - nw, errw := w.Write(buf[:n]) - cPos += nw - sum += nw - if errw == nil && nw != n { - panic("BUG: Write(p) returned (n, nil), where n != len(p)") - } - if err == nil { - err = errw - } - } - } - if err == io.EOF { - err = nil - } - if r.IsMultiRange() { - buf = buf[:0] - multiRangeBodyEnd(&buf, r.boundary) - nw, errw := w.Write(buf) - if err == nil && nw != len(buf) { - panic("BUG: buf returned (n, nil), where n != len(buf)") - } - sum += nw - err = errw - } - return int64(sum), err -} - -func multiRangeLength(sta, end []int, cl int, ct, bd string) int { - sum := 0 - hf := true - bufv := copyBufPool.Get() - buf := bufv.([]byte) - for i := range sta { - buf = buf[:0] - if i > 0 { - hf = false - } - multiRangeBodyHeader(&buf, sta[i], end[i], cl, ct, bd, hf) - sum += len(buf) - sum += end[i] - sta[i] - } - buf = buf[:0] - multiRangeBodyEnd(&buf, bd) - sum += len(buf) - copyBufPool.Put(bufv) - return sum -} - -func multiRangeBodyHeader(b *[]byte, sta, end, size int, ct, boundary string, hf bool) { - if !hf { - *b = append(*b, s2b("\r\n")...) - } - *b = append(*b, s2b(fmt.Sprintf("--%s\r\n", boundary))...) - *b = append(*b, s2b(fmt.Sprintf("%s: %s\r\n", HeaderContentRange, - fmt.Sprintf("bytes %d-%d/%d", sta, end-1, size)))...) - *b = append(*b, s2b(fmt.Sprintf("%s: %s\r\n", HeaderContentType, ct))...) - *b = append(*b, s2b("\r\n")...) -} - -func multiRangeBodyEnd(b *[]byte, boundary string) { - *b = append(*b, s2b(fmt.Sprintf("\r\n--%s--\r\n", boundary))...) -} - -func randomBoundary() string { - var buf [30]byte - _, err := io.ReadFull(rand.Reader, buf[:]) - if err != nil { - panic(err) - } - return fmt.Sprintf("%x", buf[:]) -} diff --git a/fs_test.go b/fs_test.go index 053f3b0923..22a9b33c2b 100644 --- a/fs_test.go +++ b/fs_test.go @@ -10,7 +10,6 @@ import ( "path" "runtime" "sort" - "strings" "testing" "time" ) @@ -302,7 +301,7 @@ func TestServeFileUncompressed(t *testing.T) { } } -func TestFSSingleByteRangeConcurrent(t *testing.T) { +func TestFSByteRangeConcurrent(t *testing.T) { // This test can't run parallel as files in / might by changed by other tests. stop := make(chan struct{}) @@ -320,10 +319,8 @@ func TestFSSingleByteRangeConcurrent(t *testing.T) { for i := 0; i < concurrency; i++ { go func() { for j := 0; j < 5; j++ { - testFSSingleByteRangeOfRead(t, h, "/fs.go") - testFSSingleByteRangeOfWriteTo(t, h, "/fs.go") - testFSSingleByteRangeOfRead(t, h, "/README.md") - testFSSingleByteRangeOfWriteTo(t, h, "/README.md") + testFSByteRange(t, h, "/fs.go") + testFSByteRange(t, h, "/README.md") } ch <- struct{}{} }() @@ -351,33 +348,28 @@ func TestFSByteRangeSingleThread(t *testing.T) { } h := fs.NewRequestHandler() - testFSSingleByteRangeOfRead(t, h, "/fs.go") - testFSSingleByteRangeOfWriteTo(t, h, "/fs.go") - testFSSingleByteRangeOfRead(t, h, "/README.md") - testFSSingleByteRangeOfWriteTo(t, h, "/README.md") + testFSByteRange(t, h, "/fs.go") + testFSByteRange(t, h, "/README.md") } -func testFSSingleByteRangeOfRead(t *testing.T, h RequestHandler, filePath string) { +func testFSByteRange(t *testing.T, h RequestHandler, filePath string) { var ctx RequestCtx ctx.Init(&Request{}, nil, nil) expectedBody, err := getFileContents(filePath) if err != nil { - t.Fatalf("cannot read file %q: %s", filePath, err) + t.Fatalf("cannot read file %q: %v", filePath, err) } fileSize := len(expectedBody) - startPos, endPos := make([]int, 0), make([]int, 0) - start := rand.Intn(fileSize) - end := rand.Intn(fileSize) - if end < start { - start, end = end, start + startPos := rand.Intn(fileSize) + endPos := rand.Intn(fileSize) + if endPos < startPos { + startPos, endPos = endPos, startPos } - startPos = append(startPos, start) - endPos = append(endPos, end) ctx.Request.SetRequestURI(filePath) - ctx.Request.Header.SetByteRanges(startPos, endPos) + ctx.Request.Header.SetByteRange(startPos, endPos) h(&ctx) var resp Response @@ -389,91 +381,23 @@ func testFSSingleByteRangeOfRead(t *testing.T, h RequestHandler, filePath string if resp.StatusCode() != StatusPartialContent { t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", resp.StatusCode(), StatusPartialContent, filePath) } - cr := resp.Header.Peek(HeaderContentRange) - expectedCR := fmt.Sprintf("bytes %d-%d/%d", start, end, fileSize) + expectedCR := fmt.Sprintf("bytes %d-%d/%d", startPos, endPos, fileSize) if string(cr) != expectedCR { t.Fatalf("unexpected content-range %q. Expecting %q. filePath=%q", cr, expectedCR, filePath) } body := resp.Body() - bodySize := end - start + 1 + bodySize := endPos - startPos + 1 if len(body) != bodySize { - t.Fatalf("unexpected body size %d. Expecting %d. filePath=%q, start=%d, end=%d", - len(body), bodySize, filePath, start, end) + t.Fatalf("unexpected body size %d. Expecting %d. filePath=%q, startPos=%d, endPos=%d", + len(body), bodySize, filePath, startPos, endPos) } - expectedBody = expectedBody[start : end+1] + expectedBody = expectedBody[startPos : endPos+1] if !bytes.Equal(body, expectedBody) { - t.Fatalf("unexpected body %q. Expecting %q. filePath=%q, start=%d, end=%d", - body, expectedBody, filePath, start, end) - } -} - -func testFSSingleByteRangeOfWriteTo(t *testing.T, h RequestHandler, filePath string) { - var ctx RequestCtx - ctx.Init(&Request{}, nil, nil) - - expectedBody, err := getFileContents(filePath) - if err != nil { - t.Fatalf("cannot read file %q: %s", filePath, err) - } - - fileSize := len(expectedBody) - startPos, endPos := make([]int, 0), make([]int, 0) - start := rand.Intn(fileSize) - end := rand.Intn(fileSize) - if end < start { - start, end = end, start - } - startPos = append(startPos, start) - endPos = append(endPos, end) - - ctx.Request.SetRequestURI(filePath) - ctx.Request.Header.SetByteRanges(startPos, endPos) - h(&ctx) - - bodySize := end - start + 1 - - // test WriteTo(w io.Writer) - if fileSize > maxSmallFileSize { - reader, ok := ctx.Response.bodyStream.(*bigRangeReader) - if !ok { - t.Fatal("expected bigRangeReader") - } - buf := bytes.NewBuffer(nil) - - n, err := reader.WriteTo(pureWriter{buf}) - if err != nil { - t.Fatal(err) - } - if n != int64(bodySize) { - t.Fatalf("expected %d bytes, got %d bytes", bodySize, n) - } - body1 := buf.String() - if body1 != b2s(expectedBody[start:end+1]) { - t.Fatalf("unexpected body %q. Expecting %q. filePath=%q, start=%d, end=%d", - body1, b2s(expectedBody[start:end+1]), filePath, start, end) - } - } else { - reader, ok := ctx.Response.bodyStream.(*smallRangeReader) - if !ok { - t.Fatal("expected smallRangeReader") - } - buf := bytes.NewBuffer(nil) - - n, err := reader.WriteTo(pureWriter{buf}) - if err != nil { - t.Fatal(err) - } - if n != int64(bodySize) { - t.Fatalf("expected %d bytes, got %d bytes", bodySize, n) - } - body1 := buf.String() - if body1 != b2s(expectedBody[start:end+1]) { - t.Fatalf("unexpected body %q. Expecting %q. filePath=%q, start=%d, end=%d", - body1, b2s(expectedBody[start:end+1]), filePath, start, end) - } + t.Fatalf("unexpected body %q. Expecting %q. filePath=%q, startPos=%d, endPos=%d", + body, expectedBody, filePath, startPos, endPos) } } @@ -487,325 +411,36 @@ func getFileContents(path string) ([]byte, error) { return io.ReadAll(f) } -func TestFSMultiByteRangeConcurrent(t *testing.T) { - t.Parallel() - - fs := &FS{ - Root: ".", - AcceptByteRange: true, - } - h := fs.NewRequestHandler() - - concurrency := 10 - ch := make(chan struct{}, concurrency) - for i := 0; i < concurrency; i++ { - go func() { - for j := 0; j < 5; j++ { - testFSMultiByteRangeOfRead(t, h, "/fs.go") - testFSMultiByteRangeOfWriteTo(t, h, "/fs.go") - } - ch <- struct{}{} - }() - } - - for i := 0; i < concurrency; i++ { - select { - case <-time.After(time.Second): - t.Fatalf("timeout") - case <-ch: - } - } -} - -func TestFSMultiByteRangeSingleThread(t *testing.T) { - t.Parallel() - - fs := &FS{ - Root: ".", - AcceptByteRange: true, - } - h := fs.NewRequestHandler() - - testFSMultiByteRangeOfRead(t, h, "/fs.go") - testFSMultiByteRangeOfWriteTo(t, h, "/fs.go") -} - -func testFSMultiByteRangeOfWriteTo(t *testing.T, h RequestHandler, filePath string) { - var ctx RequestCtx - ctx.Init(&Request{}, nil, nil) - - expectedBody, err := getFileContents(filePath) - if err != nil { - t.Fatalf("cannot read file %q: %s", filePath, err) - } - - num := rand.Intn(5) + 2 - - fileSize := len(expectedBody) - startPos, endPos := make([]int, 0), make([]int, 0) - - for i := 0; i < num; i++ { - start := rand.Intn(fileSize) - end := rand.Intn(fileSize) - if end < start { - start, end = end, start - } - startPos = append(startPos, start) - endPos = append(endPos, end) - } - - ctx.Request.SetRequestURI(filePath) - ctx.Request.Header.SetByteRanges(startPos, endPos) - h(&ctx) - - var body string - var boundary string - - if fileSize > maxSmallFileSize { - reader, ok := ctx.Response.bodyStream.(*bigRangeReader) - boundary = reader.Boundary() - if !ok { - t.Fatal("expected bigRangeReader") - } - buf := bytes.NewBuffer(nil) - - _, err := reader.WriteTo(pureWriter{buf}) - if err != nil { - t.Fatal(err) - } - body = buf.String() - } else { - reader, ok := ctx.Response.bodyStream.(*smallRangeReader) - boundary = reader.Boundary() - if !ok { - t.Fatal("expected smallRangeReader") - } - buf := bytes.NewBuffer(nil) - - _, err := reader.WriteTo(pureWriter{buf}) - if err != nil { - t.Fatal(err) - } - body = buf.String() - } - - singleBodys := make([]byte, 0) - - // compare with single range - for i := 0; i < num; i++ { - var ctx1 RequestCtx - ctx1.Init(&Request{}, nil, nil) - ctx1.Request.SetRequestURI(filePath) - ctx1.Request.Header.SetByteRanges([]int{startPos[i]}, []int{endPos[i]}) - h(&ctx1) - - var r1 Response - s1 := ctx1.Response.String() - - br1 := bufio.NewReader(bytes.NewBufferString(s1)) - if err1 := r1.Read(br1); err1 != nil { - t.Fatalf("unexpected error: %s. filePath=%q", err1, filePath) - } - if r1.StatusCode() != StatusPartialContent { - t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", r1.StatusCode(), StatusPartialContent, filePath) - } - - cr1 := r1.Header.Peek(HeaderContentRange) - expectedCR1 := fmt.Sprintf("bytes %d-%d/%d", startPos[i], endPos[i], fileSize) - if string(cr1) != expectedCR1 { - t.Fatalf("unexpected content-range %q. Expecting %q. filePath=%q", cr1, expectedCR1, filePath) - } - - body1 := r1.Body() - bodySize := endPos[i] - startPos[i] + 1 - if len(body1) != bodySize { - t.Fatalf("unexpected body size %d. Expecting %d. filePath=%q, startPos=%d, endPos=%d", - len(body), bodySize, filePath, startPos[i], endPos[i]) - } - - expectedBody1 := expectedBody[startPos[i] : endPos[i]+1] - if !bytes.Equal(body1, expectedBody1) { - t.Fatalf("unexpected body %q. Expecting %q. filePath=%q, startPos=%d, endPos=%d", - body1, expectedBody1, filePath, startPos[i], endPos[i]) - } - buf := make([]byte, 0) - first := true - if i > 0 { - first = false - } - ct1 := r1.Header.Peek(HeaderContentType) - multiRangeBodyHeader(&buf, startPos[i], endPos[i]+1, fileSize, string(ct1), boundary, first) - singleBodys = append(singleBodys, buf...) - singleBodys = append(singleBodys, body1...) - } - buf := make([]byte, 0) - multiRangeBodyEnd(&buf, boundary) - singleBodys = append(singleBodys, buf...) - if body != string(singleBodys) { - t.Fatalf("multipart ranges content is invalid") - } -} - -func testFSMultiByteRangeOfRead(t *testing.T, h RequestHandler, filePath string) { - var ctx RequestCtx - ctx.Init(&Request{}, nil, nil) - - expectedBody, err := getFileContents(filePath) - if err != nil { - t.Fatalf("cannot read file %q: %s", filePath, err) - } - - num := rand.Intn(5) + 2 - - fileSize := len(expectedBody) - startPos, endPos := make([]int, 0), make([]int, 0) - - for i := 0; i < num; i++ { - start := rand.Intn(fileSize) - end := rand.Intn(fileSize) - if end < start { - start, end = end, start - } - startPos = append(startPos, start) - endPos = append(endPos, end) - } - - ctx.Request.SetRequestURI(filePath) - ctx.Request.Header.SetByteRanges(startPos, endPos) - h(&ctx) - - var resp Response - s := ctx.Response.String() - - br := bufio.NewReader(bytes.NewBufferString(s)) - if err := resp.Read(br); err != nil { - t.Fatalf("unexpected error: %s. filePath=%q", err, filePath) - } - if resp.StatusCode() != StatusPartialContent { - t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", resp.StatusCode(), StatusPartialContent, filePath) - } - - ct := resp.Header.Peek(HeaderContentType) - expectedCT := "multipart/byteranges; boundary=" - if !strings.HasPrefix(string(ct), expectedCT) { - t.Fatalf("unexpected content-type %q. Expecting prefix %q. filePath=%q", ct, expectedCT, filePath) - } - - cl := resp.Header.Peek(HeaderContentLength) - - body := resp.Body() - if fmt.Sprintf("%d", len(body)) != b2s(cl) { - t.Fatalf("Content-Length error") - } - - boundary := string(ct)[len(expectedCT):] - - singleBodys := make([]byte, 0) - - // compare with single range - for i := 0; i < num; i++ { - var ctx1 RequestCtx - ctx1.Init(&Request{}, nil, nil) - ctx1.Request.SetRequestURI(filePath) - ctx1.Request.Header.SetByteRanges([]int{startPos[i]}, []int{endPos[i]}) - h(&ctx1) - - var r1 Response - s1 := ctx1.Response.String() - - br1 := bufio.NewReader(bytes.NewBufferString(s1)) - if err1 := r1.Read(br1); err1 != nil { - t.Fatalf("unexpected error: %s. filePath=%q", err1, filePath) - } - if r1.StatusCode() != StatusPartialContent { - t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", r1.StatusCode(), StatusPartialContent, filePath) - } - - cr1 := r1.Header.Peek(HeaderContentRange) - expectedCR1 := fmt.Sprintf("bytes %d-%d/%d", startPos[i], endPos[i], fileSize) - if string(cr1) != expectedCR1 { - t.Fatalf("unexpected content-range %q. Expecting %q. filePath=%q", cr1, expectedCR1, filePath) - } - - body1 := r1.Body() - bodySize := endPos[i] - startPos[i] + 1 - if len(body1) != bodySize { - t.Fatalf("unexpected body size %d. Expecting %d. filePath=%q, startPos=%d, endPos=%d", - len(body), bodySize, filePath, startPos[i], endPos[i]) - } - - expectedBody1 := expectedBody[startPos[i] : endPos[i]+1] - if !bytes.Equal(body1, expectedBody1) { - t.Fatalf("unexpected body %q. Expecting %q. filePath=%q, startPos=%d, endPos=%d", - body1, expectedBody1, filePath, startPos[i], endPos[i]) - } - buf := make([]byte, 0) - first := true - if i > 0 { - first = false - } - ct1 := r1.Header.Peek(HeaderContentType) - multiRangeBodyHeader(&buf, startPos[i], endPos[i]+1, fileSize, string(ct1), boundary, first) - singleBodys = append(singleBodys, buf...) - singleBodys = append(singleBodys, body1...) - } - buf := make([]byte, 0) - multiRangeBodyEnd(&buf, boundary) - singleBodys = append(singleBodys, buf...) - if string(body) != string(singleBodys) { - t.Fatalf("multipart ranges content is invalid") - } -} - -func TestParseByteSingleRangeSuccess(t *testing.T) { +func TestParseByteRangeSuccess(t *testing.T) { t.Parallel() - testParseByteRangeSuccess(t, "bytes=0-0", 1, []int{0}, []int{0}) - testParseByteRangeSuccess(t, "bytes=1234-6789", 6790, []int{1234}, []int{6789}) - - testParseByteRangeSuccess(t, "bytes=123-", 456, []int{123}, []int{455}) - testParseByteRangeSuccess(t, "bytes=-1", 1, []int{0}, []int{0}) - testParseByteRangeSuccess(t, "bytes=-123", 456, []int{333}, []int{455}) - - // End position exceeding content-length. It should be updated to content-length-1. - // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 - testParseByteRangeSuccess(t, "bytes=1-2345", 234, []int{1}, []int{233}) - testParseByteRangeSuccess(t, "bytes=0-2345", 2345, []int{0}, []int{2344}) - - // Start position overflow. Whole range must be returned. - // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 - testParseByteRangeSuccess(t, "bytes=-567", 56, []int{0}, []int{55}) -} - -func TestParseByteMultiRangeSuccess(t *testing.T) { - t.Parallel() + testParseByteRangeSuccess(t, "bytes=0-0", 1, 0, 0) + testParseByteRangeSuccess(t, "bytes=1234-6789", 6790, 1234, 6789) - testParseByteRangeSuccess(t, "bytes=1234-6789,23-342", 6790, []int{1234, 23}, []int{6789, 342}) - testParseByteRangeSuccess(t, "bytes=123-,-123", 456, []int{123, 333}, []int{455, 455}) + testParseByteRangeSuccess(t, "bytes=123-", 456, 123, 455) + testParseByteRangeSuccess(t, "bytes=-1", 1, 0, 0) + testParseByteRangeSuccess(t, "bytes=-123", 456, 333, 455) // End position exceeding content-length. It should be updated to content-length-1. // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 - testParseByteRangeSuccess(t, "bytes=1-2345,1-345", 234, []int{1, 1}, []int{233, 233}) - - testParseByteRangeSuccess(t, "bytes=0-2345,23-1234", 2345, []int{0, 23}, []int{2344, 1234}) + testParseByteRangeSuccess(t, "bytes=1-2345", 234, 1, 233) + testParseByteRangeSuccess(t, "bytes=0-2345", 2345, 0, 2344) // Start position overflow. Whole range must be returned. // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 - testParseByteRangeSuccess(t, "bytes=-567,-765", 56, []int{0, 0}, []int{55, 55}) + testParseByteRangeSuccess(t, "bytes=-567", 56, 0, 55) } -func testParseByteRangeSuccess(t *testing.T, v string, contentLength int, startPos, endPos []int) { - startPos1, endPos1, err := ParseByteRanges([]byte(v), contentLength) +func testParseByteRangeSuccess(t *testing.T, v string, contentLength, startPos, endPos int) { + startPos1, endPos1, err := ParseByteRange([]byte(v), contentLength) if err != nil { - t.Fatalf("unexpected error: %s. v=%q, contentLength=%d", err, v, contentLength) + t.Fatalf("unexpected error: %v. v=%q, contentLength=%d", err, v, contentLength) } - for i := range startPos1 { - if startPos1[i] != startPos[i] { - t.Fatalf("unexpected startPos=%d. Expecting %d. v=%q, contentLength=%d", startPos1[i], startPos[i], v, contentLength) - } - if endPos1[i] != endPos[i] { - t.Fatalf("unexpected endPos=%d. Expectind %d. v=%q, contentLength=%d", endPos1[i], endPos[i], v, contentLength) - } + if startPos1 != startPos { + t.Fatalf("unexpected startPos=%d. Expecting %d. v=%q, contentLength=%d", startPos1, startPos, v, contentLength) + } + if endPos1 != endPos { + t.Fatalf("unexpected endPos=%d. Expectind %d. v=%q, contentLenght=%d", endPos1, endPos, v, contentLength) } } @@ -826,6 +461,9 @@ func TestParseByteRangeError(t *testing.T) { testParseByteRangeError(t, "bytes=1-foobar", 123) testParseByteRangeError(t, "bytes=df-344", 545) + // multiple byte ranges + testParseByteRangeError(t, "bytes=1-2,4-6", 123) + // byte range exceeding contentLength testParseByteRangeError(t, "bytes=123-", 12) @@ -834,7 +472,7 @@ func TestParseByteRangeError(t *testing.T) { } func testParseByteRangeError(t *testing.T, v string, contentLength int) { - _, _, err := ParseByteRanges([]byte(v), contentLength) + _, _, err := ParseByteRange([]byte(v), contentLength) if err == nil { t.Fatalf("expecting error when parsing byte range %q", v) } diff --git a/header.go b/header.go index 57fa701d6b..7c12308956 100644 --- a/header.go +++ b/header.go @@ -113,33 +113,24 @@ func (h *ResponseHeader) SetContentRange(startPos, endPos, contentLength int) { // // - If startPos is negative, then 'bytes=-startPos' value is set. // - If endPos is negative, then 'bytes=startPos-' value is set. -func (h *RequestHeader) SetByteRanges(startPos, endPos []int) { +func (h *RequestHeader) SetByteRange(startPos, endPos int) { b := h.bufKV.value[:0] b = append(b, strBytes...) b = append(b, '=') - for i := range startPos { - if i > 0 { - b = append(b, ',') - } - if startPos[i] >= 0 { - b = AppendUint(b, startPos[i]) - } else { - endPos[i] = -startPos[i] - } - b = append(b, '-') - if endPos[i] >= 0 { - b = AppendUint(b, endPos[i]) - } + if startPos >= 0 { + b = AppendUint(b, startPos) + } else { + endPos = -startPos + } + b = append(b, '-') + if endPos >= 0 { + b = AppendUint(b, endPos) } h.bufKV.value = b h.setNonSpecial(strRange, h.bufKV.value) } -func (h *RequestHeader) SetByteRange(startPos, endPos int) { - h.SetByteRanges([]int{startPos}, []int{endPos}) -} - // StatusCode returns response status code. func (h *ResponseHeader) StatusCode() int { if h.statusCode == 0 { diff --git a/header_test.go b/header_test.go index 31b594117f..7d2942551d 100644 --- a/header_test.go +++ b/header_test.go @@ -974,7 +974,7 @@ func TestRequestHeaderSetByteRange(t *testing.T) { func testRequestHeaderSetByteRange(t *testing.T, startPos, endPos int, expectedV string) { var h RequestHeader - h.SetByteRanges([]int{startPos}, []int{endPos}) + h.SetByteRange(startPos, endPos) v := h.Peek(HeaderRange) if string(v) != expectedV { t.Fatalf("unexpected range: %q. Expecting %q. startPos=%d, endPos=%d", v, expectedV, startPos, endPos)