Commitd82862eaRecorded10 Jul 2026Repositorysigil-web

Wire serve-file to honor request Range headers

Message

serve-file takes an optional request; when it carries a Range header the file is served via http-response/file (206 Partial Content / 416), otherwise the whole file is returned as before, now advertising Accept-Ranges: bytes. make-static-handler threads the request through.

Requires sigil-http with http-response/file range: support (the release carrying T2) and a corresponding sigil-http pin bump before it can ship.

Changed
 src/sigil/web/static.sgl | 43 +++++++++++++++++++++++++++++--------------
 1 file changed, 29 insertions(+), 14 deletions(-)
Diff
src/sigil/web/static.sglmodified
@@ -46,20 +46,35 @@
46
;; File Serving
47
;; ============================================================
48
49
;;; Serve a single file with appropriate headers
50
;;; Returns http-response or #f if file doesn't exist
51
(define (serve-file file-path)
+49
;;; Serve a single file with appropriate headers.
+50
;;; Returns an http-response, or #f if the file doesn't exist.
+51
;;;
+52
;;; An optional `request` may be passed as a second argument. When it
+53
;;; carries a `Range:` header, the file is served with byte-range support
+54
;;; via `http-response/file` — a satisfiable range yields `206 Partial
+55
;;; Content`, an unsatisfiable one `416 Range Not Satisfiable`. Without a
+56
;;; Range header (or request) the whole file is returned as `200 OK`, now
+57
;;; advertising `Accept-Ranges: bytes` so clients know ranges are honored.
+58
(define (serve-file file-path . rest)
59
(: string? -> any?)
60
(if (file-exists? file-path)
54
(let* ((mime-type (mime-type-for-file file-path))
55
(content-type (or mime-type "application/octet-stream"))
56
(content (if (text-mime-type? mime-type)
57
(read-file-string file-path)
58
(read-file-bytes file-path))))
59
(http-response
60
status: 200
61
headers: (dict-set #{} content-type: content-type)
62
body: content))
+61
(let* ((request (if (pair? rest) (car rest) #f))
+62
(range (and request (http-request-header request "Range"))))
+63
(if range
+64
;; Honor byte ranges by streaming through http-response/file.
+65
(http-response/file file-path range: range)
+66
;; No Range: whole-file response (unchanged body handling),
+67
;; but advertise range support for future requests.
+68
(let* ((mime-type (mime-type-for-file file-path))
+69
(content-type (or mime-type "application/octet-stream"))
+70
(content (if (text-mime-type? mime-type)
+71
(read-file-string file-path)
+72
(read-file-bytes file-path))))
+73
(http-response
+74
status: 200
+75
headers: (dict-set (dict-set #{} content-type: content-type)
+76
accept-ranges: "bytes")
+77
body: content))))
78
#f))
79
80
;;; Check if MIME type is text-based
@@ -108,9 +123,9 @@
123
;; If it's a directory, try index file
124
((directory? file-path)
125
(let ((index-path (path-join file-path index-file)))
111
(serve-file index-path)))
+126
(serve-file index-path request)))
127
;; Try to serve the file
128
(else
114
(serve-file file-path)))))))))
+129
(serve-file file-path request)))))))))
130
131
))