1414module Utopia
1515 # A middleware which serves static files from the specified root directory.
1616 module Static
17- # Represents a local file on disk which can be served directly, or passed upstream to sendfile .
17+ # Represents a local static resource and constructs responses for it .
1818 class LocalFile
1919 # Initialize metadata for a file beneath a static root.
2020 # @parameter root [String] The root directory.
2121 # @parameter path [Utopia::Path | String] The path.
2222 def initialize ( root , path )
2323 @root = root
2424 @path = path
25- @etag = Digest ::SHA1 . hexdigest ( "#{ File . size ( full_path ) } #{ mtime_date } " )
26-
27- @range = nil
25+ fingerprint = Digest ::SHA1 . hexdigest ( "#{ File . size ( full_path ) } #{ mtime_date } " )
26+ @etag = %Q{"#{ fingerprint } "}
2827 end
2928
3029 attr :root
3130 attr :path
3231 attr :etag
33- attr :range
34-
35- # Expose the filesystem path for upstream sendfile support.
36- def to_path
37- full_path
38- end
3932
4033 # Resolve this file beneath its configured root.
4134 # @returns [String] The full filesystem path.
@@ -55,41 +48,16 @@ def bytesize
5548 File . size ( full_path )
5649 end
5750
58- # This reflects whether calling each would yield anything.
59- def empty?
60- bytesize == 0
61- end
62-
63- alias size bytesize
64-
65- # Enumerate the contained values.
66- # @returns [Enumerator] An enumerator over the resulting values.
67- def each
68- File . open ( full_path , "rb" ) do |file |
69- file . seek ( @range . begin )
70- remaining = @range . end - @range . begin +1
71-
72- while remaining > 0
73- break unless part = file . read ( [ 8192 , remaining ] . min )
74-
75- remaining -= part . length
76-
77- yield part
78- end
79- end
80- end
81-
8251 # Check whether the file has changed since the request validators.
8352 # @parameter request [Utopia::Request] The request.
8453 # @returns [Boolean] Whether the file is newer than the request validators.
8554 def modified? ( request )
86- if modified_since = request . headers [ "if-modified-since " ]
87- return false if File . mtime ( full_path ) <= Time . parse ( modified_since )
55+ if etags = request . headers [ "if-none-match " ]
56+ return ! etags . weak_match? ( @etag )
8857 end
8958
90- if etags = request . headers [ "if-none-match" ]
91- etags = etags . split ( /\s *,\s */ )
92- return false if etags . include? ( etag ) || etags . include? ( "*" )
59+ if modified_since = request . headers [ "if-modified-since" ]
60+ return File . mtime ( full_path ) . to_i > modified_since . to_time . to_i
9361 end
9462
9563 return true
@@ -104,6 +72,7 @@ def modified?(request)
10472 # @returns [Protocol::HTTP::Response] The response.
10573 def serve ( request , response_headers )
10674 ranges = byte_ranges ( request . headers [ "range" ] )
75+ size = bytesize
10776
10877 # puts "Requesting ranges: #{ranges.inspect} (#{size})"
10978
@@ -112,18 +81,18 @@ def serve(request, response_headers)
11281 # TODO: Support multiple byte-ranges, for now just send entire file:
11382 status = 200
11483 response_headers [ CONTENT_LENGTH ] = size . to_s
115- @ range = 0 ... size
84+ range = nil
11685 else
11786 # Partial content:
118- @ range = ranges [ 0 ]
119- partial_size = @ range. size
87+ range = ranges [ 0 ]
88+ partial_size = range . size
12089
12190 status = 206
12291 response_headers [ CONTENT_LENGTH ] = partial_size . to_s
123- response_headers [ CONTENT_RANGE ] = "bytes #{ @ range. min } -#{ @ range. max } /#{ size } "
92+ response_headers [ CONTENT_RANGE ] = "bytes #{ range . min } -#{ range . max } /#{ size } "
12493 end
12594
126- body = Protocol ::HTTP ::Body ::File . open ( full_path , status == 206 ? @ range : nil , size : size )
95+ body = Protocol ::HTTP ::Body ::File . open ( full_path , range , size : size )
12796
12897 return Response [ status , response_headers , body ]
12998 end
@@ -134,7 +103,7 @@ def serve(request, response_headers)
134103 def byte_ranges ( range )
135104 return nil unless range &.bytes?
136105
137- return range . resolve ( size )
106+ return range . resolve ( bytesize )
138107 end
139108 end
140109 end
0 commit comments