aboutsummaryrefslogtreecommitdiff
path: root/lib/http/range_position.go
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2024-01-19 03:39:50 +0700
committerShulhan <ms@kilabit.info>2024-01-24 00:07:18 +0700
commiteb2b4dbdeceb663b1ce29bc08cd5446ecf4c1f07 (patch)
treecfeb884e2890d8f74c516eaad9e0bc7a8bbdce59 /lib/http/range_position.go
parentdbfc1583d77d300f8da8e2d3714fa1592dde09a0 (diff)
downloadpakakeh.go-eb2b4dbdeceb663b1ce29bc08cd5446ecf4c1f07.tar.xz
lib/http: refactoring Range request, limit content served by server
When server receive, GET /big Range: bytes=0- and the requested resources is quite larger, where writing all content of file result in i/o timeout, it is best practice [1][2] if the server write only partial content and let the client continue with the subsequent Range request. In the above case, the server should response with, HTTP/1.1 206 Partial content Content-Range: bytes 0-<limit>/<size> Content-Length: <limit> Where limit is maximum packet that is reasonable [3] for most of the client. In this server we choose 8MB as limit. [1]: https://stackoverflow.com/questions/63614008/how-best-to-respond-to-an-open-http-range-request [2]: https://bugzilla.mozilla.org/show_bug.cgi?id=570755 [3]: https://docs.aws.amazon.com/whitepapers/latest/s3-optimizing-performance-best-practices/use-byte-range-fetches.html
Diffstat (limited to 'lib/http/range_position.go')
-rw-r--r--lib/http/range_position.go125
1 files changed, 71 insertions, 54 deletions
diff --git a/lib/http/range_position.go b/lib/http/range_position.go
index 19d9ed08..a6cc8dc0 100644
--- a/lib/http/range_position.go
+++ b/lib/http/range_position.go
@@ -12,21 +12,27 @@ import (
type RangePosition struct {
unit string
- content []byte
+ start *int64
+ end *int64
- Start int64
- End int64
+ // length of resources.
+ // A nil length, or "*", indicated an unknown size.
+ length *int64
- // Length of zero means read until the end.
- Length int64
+ content []byte
}
-// ParseContentRange parse Content-Range value, the following format,
+// ParseContentRange parse the HTTP header "Content-Range" value, as
+// response from server, with the following format,
//
-// unit SP position "/" size
-// SP = " "
-// position = start "-" end / start "-" / "-" last
-// start, end, last, size = 1*DIGIT
+// Content-Range = unit SP valid-range / invalid-range
+// SP = " "
+// valid-range = position "/" size
+// invalid-range = "*" "/" size
+// position = start "-" end
+// size = 1*DIGIT / "*"
+// start = 1*DIGIT
+// end = 1*DIGIT
//
// It will return nil if the v is invalid.
func ParseContentRange(v string) (pos *RangePosition) {
@@ -50,62 +56,70 @@ func ParseContentRange(v string) (pos *RangePosition) {
p.SetDelimiters(`-/`)
tok, delim = p.ReadNoSpace()
- if len(tok) == 0 && delim == '-' {
- // Probably "-last".
- tok, delim = p.ReadNoSpace()
+ if len(tok) == 0 {
+ return nil
+ }
+ if tok == `*` {
if delim != '/' {
return nil
}
-
- pos.Length, err = strconv.ParseInt(tok, 10, 64)
- if err != nil {
+ tok, delim = p.ReadNoSpace()
+ if delim != 0 {
return nil
}
-
- pos.Start = -1 * pos.Length
- } else {
- if delim != '-' || delim == 0 {
- return nil
+ if tok == `*` {
+ // "*/*": invalid range requested with unknown size.
+ pos = &RangePosition{}
+ return pos
}
- pos.Start, err = strconv.ParseInt(tok, 10, 64)
- if err != nil {
- return nil
- }
+ pos = &RangePosition{}
+ goto parselength
+ }
+ if delim != '-' {
+ return nil
+ }
- tok, delim = p.ReadNoSpace()
- if delim != '/' {
- return nil
- }
+ pos = &RangePosition{
+ start: new(int64),
+ end: new(int64),
+ length: new(int64),
+ }
- if len(tok) != 0 {
- // Case of "start-end/size".
- pos.End, err = strconv.ParseInt(tok, 10, 64)
- if err != nil {
- return nil
- }
- pos.Length = (pos.End - pos.Start) + 1
- }
+ *pos.start, err = strconv.ParseInt(tok, 10, 64)
+ if err != nil {
+ return nil
}
- // The size.
tok, delim = p.ReadNoSpace()
- if delim != 0 {
+ if delim != '/' {
+ return nil
+ }
+ *pos.end, err = strconv.ParseInt(tok, 10, 64)
+ if err != nil {
+ return nil
+ }
+ if *pos.end < *pos.start {
return nil
}
- if tok != "*" {
- var size int64
- size, err = strconv.ParseInt(tok, 10, 64)
- if err != nil {
- return nil
- }
- if pos.End == 0 {
- // Case of "start-/size".
- pos.Length = (size - pos.Start)
- }
+ tok, delim = p.ReadNoSpace()
+ if delim != 0 {
+ return nil
+ }
+ if tok == `*` {
+ // "x-y/*"
+ return pos
}
+parselength:
+ *pos.length, err = strconv.ParseInt(tok, 10, 64)
+ if err != nil {
+ return nil
+ }
+ if *pos.length < 0 {
+ return nil
+ }
return pos
}
@@ -126,11 +140,14 @@ func (pos RangePosition) ContentRange(unit string, size int64) (v string) {
}
func (pos RangePosition) String() string {
- if pos.Start < 0 {
- return fmt.Sprintf(`%d`, pos.Start)
+ if pos.start == nil {
+ if pos.end == nil {
+ return `*`
+ }
+ return fmt.Sprintf(`-%d`, *pos.end)
}
- if pos.Start > 0 && pos.End == 0 {
- return fmt.Sprintf(`%d-`, pos.Start)
+ if pos.end == nil {
+ return fmt.Sprintf(`%d-`, *pos.start)
}
- return fmt.Sprintf(`%d-%d`, pos.Start, pos.End)
+ return fmt.Sprintf(`%d-%d`, *pos.start, *pos.end)
}