diff --git a/ctx.go b/ctx.go index 34d49a9b11..944ea40b21 100644 --- a/ctx.go +++ b/ctx.go @@ -115,10 +115,13 @@ func (t *TLSHandler) GetClientInfo(info *tls.ClientHelloInfo) (*tls.Certificate, // Range data for c.Range type Range struct { Type string - Ranges []struct { - Start int - End int - } + Ranges []RangeSet +} + +// RangeSet represents a single content range from a request. +type RangeSet struct { + Start int + End int } // Cookie data for c.Cookie @@ -1392,25 +1395,44 @@ var ( // Range returns a struct containing the type and a slice of ranges. func (c *Ctx) Range(size int) (Range, error) { - var rangeData Range + var ( + rangeData Range + ranges string + ) rangeStr := c.Get(HeaderRange) - if rangeStr == "" || !strings.Contains(rangeStr, "=") { - return rangeData, ErrRangeMalformed - } - data := strings.Split(rangeStr, "=") - const expectedDataParts = 2 - if len(data) != expectedDataParts { + + i := strings.IndexByte(rangeStr, '=') + if i == -1 || strings.Contains(rangeStr[i+1:], "=") { return rangeData, ErrRangeMalformed } - rangeData.Type = data[0] - arr := strings.Split(data[1], ",") - for i := 0; i < len(arr); i++ { - item := strings.Split(arr[i], "-") - if len(item) == 1 { + rangeData.Type = rangeStr[:i] + ranges = rangeStr[i+1:] + + var ( + singleRange string + moreRanges = ranges + ) + for moreRanges != "" { + singleRange = moreRanges + if i := strings.IndexByte(moreRanges, ','); i >= 0 { + singleRange = moreRanges[:i] + moreRanges = moreRanges[i+1:] + } else { + moreRanges = "" + } + + var ( + startStr, endStr string + i int + ) + if i = strings.IndexByte(singleRange, '-'); i == -1 { return rangeData, ErrRangeMalformed } - start, startErr := strconv.Atoi(item[0]) - end, endErr := strconv.Atoi(item[1]) + startStr = singleRange[:i] + endStr = singleRange[i+1:] + + start, startErr := fasthttp.ParseUint(utils.UnsafeBytes(startStr)) + end, endErr := fasthttp.ParseUint(utils.UnsafeBytes(endStr)) if startErr != nil { // -nnn start = size - end end = size - 1 diff --git a/ctx_test.go b/ctx_test.go index ef9007293f..28f046990f 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -2521,39 +2521,67 @@ func Test_Ctx_Range(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - var ( - result Range - err error - ) - - _, err = c.Range(1000) - utils.AssertEqual(t, true, err != nil) - - c.Request().Header.Set(HeaderRange, "bytes=500") - _, err = c.Range(1000) - utils.AssertEqual(t, true, err != nil) + testRange := func(header string, ranges ...RangeSet) { + c.Request().Header.Set(HeaderRange, header) + result, err := c.Range(1000) + if len(ranges) == 0 { + utils.AssertEqual(t, true, err != nil) + } else { + utils.AssertEqual(t, "bytes", result.Type) + utils.AssertEqual(t, true, err == nil) + } + utils.AssertEqual(t, len(ranges), len(result.Ranges)) + for i := range ranges { + utils.AssertEqual(t, ranges[i], result.Ranges[i]) + } + } - c.Request().Header.Set(HeaderRange, "bytes=500=") - _, err = c.Range(1000) - utils.AssertEqual(t, true, err != nil) + testRange("bytes=500") + testRange("bytes=") + testRange("bytes=500=") + testRange("bytes=500-300") + testRange("bytes=a-700", RangeSet{300, 999}) + testRange("bytes=500-b", RangeSet{500, 999}) + testRange("bytes=500-1000", RangeSet{500, 999}) + testRange("bytes=500-700", RangeSet{500, 700}) + testRange("bytes=0-0,2-1000", RangeSet{0, 0}, RangeSet{2, 999}) + testRange("bytes=0-99,450-549,-100", RangeSet{0, 99}, RangeSet{450, 549}, RangeSet{900, 999}) + testRange("bytes=500-700,601-999", RangeSet{500, 700}, RangeSet{601, 999}) +} - c.Request().Header.Set(HeaderRange, "bytes=500-300") - _, err = c.Range(1000) - utils.AssertEqual(t, true, err != nil) +// go test -v -run=^$ -bench=Benchmark_Ctx_Range -benchmem -count=4 +func Benchmark_Ctx_Range(b *testing.B) { + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) - testRange := func(header string, start, end int) { - c.Request().Header.Set(HeaderRange, header) - result, err = c.Range(1000) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "bytes", result.Type) - utils.AssertEqual(t, start, result.Ranges[0].Start) - utils.AssertEqual(t, end, result.Ranges[0].End) + testCases := []struct { + str string + start int + end int + }{ + {"bytes=-700", 300, 999}, + {"bytes=500-", 500, 999}, + {"bytes=500-1000", 500, 999}, + {"bytes=0-700,800-1000", 0, 700}, + } + + for _, tc := range testCases { + b.Run(tc.str, func(b *testing.B) { + c.Request().Header.Set(HeaderRange, tc.str) + var ( + result Range + err error + ) + for n := 0; n < b.N; n++ { + result, err = c.Range(1000) + } + utils.AssertEqual(b, nil, err) + utils.AssertEqual(b, "bytes", result.Type) + utils.AssertEqual(b, tc.start, result.Ranges[0].Start) + utils.AssertEqual(b, tc.end, result.Ranges[0].End) + }) } - - testRange("bytes=a-700", 300, 999) - testRange("bytes=500-b", 500, 999) - testRange("bytes=500-1000", 500, 999) - testRange("bytes=500-700", 500, 700) } // go test -run Test_Ctx_Route