class LocalFile
Represents a local static resource and constructs responses for it.
Definitions
def initialize(path)
Initialize metadata for a local file.
Signature
-
parameter
pathString The resolved filesystem path.
Implementation
def initialize(path)
@path = path
@stat = File.stat(@path)
@mtime_date = @stat.mtime.httpdate
fingerprint = Digest::SHA1.hexdigest("#{@stat.size}:#{@stat.mtime.to_i}:#{@stat.mtime.nsec}")
@etag = %Q{W/"#{fingerprint}"}
end
def mtime_date
Format the file's modification time for an HTTP header.
Signature
-
returns
String The HTTP-date modification time.
Implementation
def mtime_date
@mtime_date
end
def bytesize
Measure the file's content length.
Signature
-
returns
Integer The file size in bytes.
Implementation
def bytesize
@stat.size
end
def modified?(request)
Check whether the file has changed since the request validators.
Signature
-
parameter
requestUtopia::Request The request.
-
returns
Boolean Whether the file is newer than the request validators.
Implementation
def modified?(request)
if etags = request.headers["if-none-match"]
return !etags.weak_match?(@etag)
end
if modified_since = request.headers["if-modified-since"]
return @stat.mtime.to_i > modified_since.to_time.to_i
end
return true
end
def serve(request, response_headers)
Serve.
Signature
-
parameter
requestUtopia::Request The request.
-
parameter
response_headersHash The response headers.
-
returns
Protocol::HTTP::Response The response.
Implementation
def serve(request, response_headers)
ranges = byte_ranges(request)
size = bytesize
# puts "Requesting ranges: #{ranges.inspect} (#{size})"
if ranges == nil or ranges.size != 1
# No ranges, or multiple ranges (which we don't support).
# TODO: Support multiple byte-ranges, for now just send entire file:
status = 200
response_headers[CONTENT_LENGTH] = size.to_s
range = nil
else
# Partial content:
range = ranges[0]
partial_size = range.size
status = 206
response_headers[CONTENT_LENGTH] = partial_size.to_s
response_headers[CONTENT_RANGE] = "bytes #{range.min}-#{range.max}/#{size}"
end
if request.head?
body = Protocol::HTTP::Body::Head.new(size)
else
body = Protocol::HTTP::Body::File.open(@path, range, size: size)
end
return Response[status, response_headers, body]
end
def byte_ranges(request)
Resolve satisfiable byte ranges from the parsed range header.
Signature
-
parameter
requestUtopia::Request The request.
-
returns
Array | Nil The resulting values, or
nilif the range is not applicable.
Implementation
def byte_ranges(request)
return nil unless request.method == Protocol::HTTP::Methods::GET
range = request.headers["range"]
return nil unless range&.bytes?
if validator = request.headers["if-range"]
return nil unless if_range?(validator)
end
return range.resolve(bytesize)
end