added range support to continue interrupted download
This commit is contained in:
@@ -2,11 +2,11 @@ package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"serveg/internal/html"
|
||||
"serveg/internal/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (conn *TcpConnection) GET_HEAD(req *http.Request) *http.Response {
|
||||
@@ -68,31 +68,59 @@ func (conn *TcpConnection) serveFile(req *http.Request, filePath string) *http.R
|
||||
}
|
||||
size := finf.Size()
|
||||
|
||||
// time is received with second precision
|
||||
// if not truncate, mtime will always be > ifModifiedSince
|
||||
mtime := finf.ModTime().UTC().Truncate(time.Second)
|
||||
|
||||
ifModifiedSince, errTime := time.Parse(http.HeaderTimeFormat, req.Headers["if-modified-since"])
|
||||
notModified := errTime == nil && !mtime.After(ifModifiedSince)
|
||||
if notModified {
|
||||
// Clients send back the exact last-modified value they received.
|
||||
// Exact match (not "<=") also detects a file replaced by one with older mtime.
|
||||
lastModified := finf.ModTime().UTC().Format(http.HeaderTimeFormat)
|
||||
if req.Headers["if-modified-since"] == lastModified {
|
||||
return conn.http.CreateResponse(http.StatusNotModified, req, false)
|
||||
}
|
||||
|
||||
res := conn.http.CreateResponse(http.StatusOK, req, false)
|
||||
// Range is used to continue interrupted downloads.
|
||||
// If-Range says "send the range only if the file wasn't changed, otherwise send the whole file".
|
||||
rangeHdr := req.Headers["range"]
|
||||
if ifRange, ok := req.Headers["if-range"]; ok && ifRange != lastModified {
|
||||
rangeHdr = ""
|
||||
}
|
||||
start, end, rangeResult := http.ParseRange(rangeHdr, size)
|
||||
|
||||
status := http.StatusOK
|
||||
bodyLen := size
|
||||
switch rangeResult {
|
||||
case http.RangeUnsatisfiable:
|
||||
return conn.createErrorResponse(req, false, http.StatusRequestedRangeNotSatisfiable,
|
||||
fmt.Sprintf("range '%s' is outside of file size %d", rangeHdr, size)).
|
||||
WithHeader("content-range", fmt.Sprintf("bytes */%d", size)).
|
||||
WithHeader("accept-ranges", "bytes")
|
||||
case http.RangeOK:
|
||||
status = http.StatusPartialContent
|
||||
bodyLen = end - start + 1
|
||||
if _, err = f.Seek(start, io.SeekStart); err != nil {
|
||||
return conn.createErrorResponse(req, false, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
res := conn.http.CreateResponse(status, req, false)
|
||||
fileName := path.Base(filePath)
|
||||
mimeType := http.GetMimeType(fileName)
|
||||
|
||||
if req.Method != "HEAD" {
|
||||
// file will be closed by SendResponse()
|
||||
fileOwnershipTransferred = true
|
||||
res.WithBodyReader(f, size, http.GetMimeType(fileName))
|
||||
res.WithBodyReader(f, bodyLen, mimeType)
|
||||
} else {
|
||||
// WithBodyReader does nothing for HEAD, but clients use HEAD to get file size
|
||||
res.WithHeaderI("content-length", bodyLen).
|
||||
WithHeader("content-type", mimeType)
|
||||
}
|
||||
if rangeResult == http.RangeOK {
|
||||
res.Headers["content-range"] = fmt.Sprintf("bytes %d-%d/%d", start, end, size)
|
||||
}
|
||||
// Tell browser to not use its own caching, remember last-modified
|
||||
// On next request browser will send if-modified-since and let server check if file changed.
|
||||
res.Headers["cache-control"] = "no-cache"
|
||||
res.Headers["last-modified"] = mtime.Format(http.HeaderTimeFormat)
|
||||
res.Headers["last-modified"] = lastModified
|
||||
res.Headers["content-disposition"] = http.ContentDisposition("inline", fileName)
|
||||
//TODO: accept "range" header and return StatusPartialContent
|
||||
// res.Headers["accept-ranges"] = "bytes"
|
||||
res.Headers["accept-ranges"] = "bytes"
|
||||
|
||||
return res
|
||||
}
|
||||
@@ -104,7 +104,7 @@ func (conn *TcpConnection) handleUpload(req *http.Request, filePath string, file
|
||||
|
||||
// OPTIONS
|
||||
if !reqHasBody {
|
||||
uploadSize, err := strconv.ParseUint(req.Headers["x-upload-size"], 10, 64)
|
||||
uploadSize, err := strconv.ParseUint(req.Headers["x-upload-size"], 10, 63)
|
||||
if err != nil {
|
||||
return createResponseMsg(http.StatusBadRequest,
|
||||
"OPTIONS request must have valid header x-upload-size")
|
||||
Reference in New Issue
Block a user