aboutsummaryrefslogtreecommitdiff
path: root/lib/http/server.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/server.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/server.go')
-rw-r--r--lib/http/server.go80
1 files changed, 57 insertions, 23 deletions
diff --git a/lib/http/server.go b/lib/http/server.go
index 55674038..a17c736c 100644
--- a/lib/http/server.go
+++ b/lib/http/server.go
@@ -784,7 +784,7 @@ func (srv *Server) handlePut(res http.ResponseWriter, req *http.Request) {
// - 416 StatusRequestedRangeNotSatisfiable, if the request Range start
// position is greater than resource size.
//
-// [HTTP Range]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
+// [HTTP Range]: https://datatracker.ietf.org/doc/html/rfc7233
// [RFC7233 S-3.1]: https://datatracker.ietf.org/doc/html/rfc7233#section-3.1
func HandleRange(res http.ResponseWriter, req *http.Request, bodyReader io.ReadSeeker, contentType string) {
var (
@@ -823,46 +823,76 @@ func handleRange(res http.ResponseWriter, req *http.Request, bodyReader io.ReadS
}
var (
- size int64
- err error
+ size int64
+ nread int64
+ err error
)
+
size, err = bodyReader.Seek(0, io.SeekEnd)
if err != nil {
// An error here assume that the size is unknown ('*').
log.Printf(`%s: seek body size: %s`, logp, err)
+ res.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
+ return
}
var (
- listPos = r.Positions()
- listBody = make([][]byte, 0, len(listPos))
+ header = res.Header()
+ listBody = make([][]byte, 0, len(r.positions))
- pos RangePosition
+ pos *RangePosition
)
- for _, pos = range listPos {
- if pos.Start < 0 {
- _, err = bodyReader.Seek(pos.Start, io.SeekEnd)
- } else {
- _, err = bodyReader.Seek(pos.Start, io.SeekStart)
+ for _, pos = range r.positions {
+ // Refill the position if its nil, for response later,
+ // calculate the number of bytes to read, and move the file
+ // position for read.
+ if pos.start == nil {
+ pos.start = new(int64)
+ if *pos.end > size {
+ *pos.start = 0
+ } else {
+ *pos.start = size - *pos.end
+ }
+ *pos.end = size - 1
+ } else if pos.end == nil {
+ if *pos.start > size {
+ // rfc7233#section-4.4
+ // the first-byte-pos of all of the
+ // byte-range-spec values were greater than
+ // the current length of the selected
+ // representation.
+ pos.start = nil
+ header.Set(HeaderContentRange, pos.ContentRange(r.unit, size))
+ res.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
+ return
+ }
+ pos.end = new(int64)
+ *pos.end = size - 1
}
+
+ _, err = bodyReader.Seek(*pos.start, io.SeekStart)
if err != nil {
- log.Printf(`%s: seek %d: %s`, logp, pos.Start, err)
+ log.Printf(`%s: seek %s: %s`, logp, pos, err)
res.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return
}
+ nread = (*pos.end - *pos.start) + 1
+
+ if nread > DefRangeLimit {
+ nread = DefRangeLimit
+ *pos.end = *pos.start + nread
+ }
+
var (
- body []byte
+ body = make([]byte, nread)
n int
)
- if pos.Length > 0 {
- body = make([]byte, pos.Length)
- } else {
- body = make([]byte, size)
- }
n, err = bodyReader.Read(body)
if n == 0 || err != nil {
- log.Printf(`%s: seek %d, size %d: %s`, logp, pos.Start, size, err)
+ log.Printf(`%s: range %s/%d: %s`, logp, pos, size, err)
+ header.Set(HeaderContentRange, pos.ContentRange(r.unit, size))
res.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
return
}
@@ -870,11 +900,15 @@ func handleRange(res http.ResponseWriter, req *http.Request, bodyReader io.ReadS
listBody = append(listBody, body)
}
- var header = res.Header()
-
if len(listBody) == 1 {
- pos = listPos[0]
+ var (
+ body = listBody[0]
+ nbody = strconv.FormatInt(int64(len(body)), 10)
+ )
+ pos = r.positions[0]
+ header.Set(HeaderContentLength, nbody)
header.Set(HeaderContentRange, pos.ContentRange(r.unit, size))
+ header.Set(HeaderContentType, contentType)
res.WriteHeader(http.StatusPartialContent)
_, err = res.Write(listBody[0])
if err != nil {
@@ -890,7 +924,7 @@ func handleRange(res http.ResponseWriter, req *http.Request, bodyReader io.ReadS
x int
)
- for x, pos = range listPos {
+ for x, pos = range r.positions {
fmt.Fprintf(&bb, "--%s\r\n", boundary)
fmt.Fprintf(&bb, "%s: %s\r\n", HeaderContentType, contentType)
fmt.Fprintf(&bb, "%s: %s\r\n\r\n", HeaderContentRange, pos.ContentRange(r.unit, size))