class LocalFile
Represents a local file on disk which can be served directly, or passed upstream to sendfile.
Definitions
def initialize(root, path)
Initialize metadata for a file beneath a static root.
Signature
-
parameter
rootString The root directory.
-
parameter
pathUtopia::Path | String The path.
Implementation
def initialize(root, path)
@root = root
@path = path
@etag = Digest::SHA1.hexdigest("#{File.size(full_path)}#{mtime_date}")
@range = nil
end
def to_path
Fit in with Rack::Sendfile
Implementation
def to_path
full_path
end
def full_path
Resolve this file beneath its configured root.
Signature
-
returns
String The full filesystem path.
Implementation
def full_path
File.join(@root, @path.components)
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
File.mtime(full_path).httpdate
end
def bytesize
Measure the file's content length.
Signature
-
returns
Integer The file size in bytes.
Implementation
def bytesize
File.size(full_path)
end
def empty?
This reflects whether calling each would yield anything.
Implementation
def empty?
bytesize == 0
end
def each
Enumerate the contained values.
Signature
-
returns
Enumerator An enumerator over the resulting values.
Implementation
def each
File.open(full_path, "rb") do |file|
file.seek(@range.begin)
remaining = @range.end - @range.begin+1
while remaining > 0
break unless part = file.read([8192, remaining].min)
remaining -= part.length
yield part
end
end
end
def modified?(env)
Check whether the file has changed since the request validators.
Signature
-
parameter
envHash The Rack environment.
-
returns
Boolean Whether the file is newer than the request validators.
Implementation
def modified?(env)
if modified_since = env["HTTP_IF_MODIFIED_SINCE"]
return false if File.mtime(full_path) <= Time.parse(modified_since)
end
if etags = env["HTTP_IF_NONE_MATCH"]
etags = etags.split(/\s*,\s*/)
return false if etags.include?(etag) || etags.include?("*")
end
return true
end
def serve(env, response_headers)
Serve the file, honoring a single byte range when requested.
Signature
-
parameter
envHash The Rack environment.
-
parameter
response_headersHash The response headers.
-
returns
Array The Rack response.
Implementation
def serve(env, response_headers)
ranges = Rack::Utils.get_byte_ranges(env["HTTP_RANGE"], size)
response = [200, response_headers, self]
# 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:
response[0] = 200
response[1][CONTENT_LENGTH] = size.to_s
@range = 0...size
else
# Partial content:
@range = ranges[0]
partial_size = @range.size
response[0] = 206
response[1][CONTENT_LENGTH] = partial_size.to_s
response[1][CONTENT_RANGE] = "bytes #{@range.min}-#{@range.max}/#{size}"
end
return response
end