diff options
| author | Shulhan <ms@kilabit.info> | 2024-01-19 03:39:50 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2024-01-24 00:07:18 +0700 |
| commit | eb2b4dbdeceb663b1ce29bc08cd5446ecf4c1f07 (patch) | |
| tree | cfeb884e2890d8f74c516eaad9e0bc7a8bbdce59 /lib/http/server.go | |
| parent | dbfc1583d77d300f8da8e2d3714fa1592dde09a0 (diff) | |
| download | pakakeh.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.go | 80 |
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)) |
