std/fileserver
import std/fileserver to use it. Source: src/std/fileserver.iyi at v0.18.0.
The type is std/mime's for the extension, text and JSON with charset=utf-8. Caching is Last-Modified and an ETag made of the modification time and the size in hex: If-None-Match (weakly compared) and, without it, If-Modified-Since answer 304 (§13.2.2 orders them so). One Range: bytes= range is a 206 with its Content-Range, a suffix range included; one that starts past the end is 416 with bytes */size; several ranges, another unit, or a range under an If-Range that names another version is the whole file, which §14.2 allows. The file is copied out by the server as it is read, never whole, from where the range starts.
The request path is percent-decoded and must stay below root: a .. segment, a NUL and a backslash are 404s, the same answer a file that is not there gets, so what lies outside is not told apart from what does not exist. Symbolic links inside root are followed, as Go's are. A directory is its index file, and one asked for without its trailing / is redirected to it (301), so the links in its index resolve. Methods other than GET and HEAD are 405 with Allow: GET, HEAD.
A module of its own beside std/http, since it reads files and the clock and std/http reaches the network alone.
FileServerstruct
pub struct FileServer
# One `bytes=` range of a *size*-byte file: its first and last byte,
# `true` for one that starts past the end (416), nil for anything else
# - another unit, several ranges, a range that is not one - which is
# served as the whole file.
def self.byte_range(value : String, size : Int64) : ::Tuple(Int64, Int64) | Bool | Nil
def initialize
# A `Range` is honoured on a GET, and under `If-Range` only when it
# names this version - its tag, or its date exactly (§13.1.5).
protected def self.range_applies?(req : Request, tag : String, stamp : String) : Bool
# The answer to *req* from the files under *root*: what a server's
# block returns to serve a directory. A GET or HEAD only, 405 otherwise.
# The path is percent-decoded and read below *root*: a `..` segment, a
# NUL or a backslash is a 404 rather than a way out of it. A directory
# is its *index* file, and one asked for without its trailing `/` is
# redirected there, so relative links inside resolve. The type comes
# from the extension (std/mime), text with `charset=utf-8`.
#
# Caching and ranges are RFC 9110's: `Last-Modified` and an `ETag` made
# from the modification time and size; `If-None-Match`, and without it
# `If-Modified-Since`, answer 304. One `Range: bytes=` range is a 206
# with its `Content-Range`, one past the end a 416; several are served
# as the whole file (§14.2 lets a server ignore a range), and so is a
# range whose `If-Range` names another version. The file is read as it
# is sent, never whole.
def self.serve(root : String, req : Request, index : String = "index.html") : Response
# Whether an `If-None-Match` list names *tag*, weakly compared (§13.1.2).
protected def self.tag_listed?(list : String, tag : String) : Bool
end
Files served from a directory: what a server's block answers with
FileServer.serve(root, request)for the requests it means to be files. Go'shttp.FileServer, and the same RFC 9110 rules.