Class: Rack::Files
- Inherits:
-
Object
- Object
- Rack::Files
- Defined in:
- lib/rack/files.rb
Overview
Rack::Files serves files below the root
directory given, according to the path info of the Rack request. e.g. when Rack::Files.new(“/etc”) is used, you can access 'passwd' file as localhost:9292/passwd
Handlers can detect if bodies are a Rack::Files, and use mechanisms like sendfile on the path
.
Defined Under Namespace
Classes: BaseIterator, Iterator
Constant Summary collapse
- ALLOWED_VERBS =
%w[GET HEAD OPTIONS]
- ALLOW_HEADER =
ALLOWED_VERBS.join(', ')
- MULTIPART_BOUNDARY =
'AaB03x'
Instance Attribute Summary collapse
-
#root ⇒ Object
readonly
Returns the value of attribute root.
Class Method Summary collapse
Instance Method Summary collapse
- #call(env) ⇒ Object
- #get(env) ⇒ Object
-
#initialize(root, headers = {}, default_mime = 'text/plain') ⇒ Files
constructor
A new instance of Files.
- #serving(request, path) ⇒ Object
Constructor Details
#initialize(root, headers = {}, default_mime = 'text/plain') ⇒ Files
Returns a new instance of Files.
29 30 31 32 33 34 |
# File 'lib/rack/files.rb', line 29 def initialize(root, headers = {}, default_mime = 'text/plain') @root = (::File.(root) if root) @headers = headers @default_mime = default_mime @head = Rack::Head.new(lambda { |env| get env }) end |
Instance Attribute Details
#root ⇒ Object (readonly)
Returns the value of attribute root.
27 28 29 |
# File 'lib/rack/files.rb', line 27 def root @root end |
Class Method Details
.method_added(name) ⇒ Object
TODO:
remove in 3.0
20 21 22 23 24 25 |
# File 'lib/rack/files.rb', line 20 def self.method_added(name) if name == :response_body raise "#{self.class}\#response_body is no longer supported." end super end |
Instance Method Details
#call(env) ⇒ Object
36 37 38 39 |
# File 'lib/rack/files.rb', line 36 def call(env) # HEAD requests drop the response body, including 4xx error messages. @head.call env end |
#get(env) ⇒ Object
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/rack/files.rb', line 41 def get(env) request = Rack::Request.new env unless ALLOWED_VERBS.include? request.request_method return fail(405, "Method Not Allowed", { 'Allow' => ALLOW_HEADER }) end path_info = Utils.unescape_path request.path_info return fail(400, "Bad Request") unless Utils.valid_path?(path_info) clean_path_info = Utils.clean_path_info(path_info) path = ::File.join(@root, clean_path_info) available = begin ::File.file?(path) && ::File.readable?(path) rescue SystemCallError # Not sure in what conditions this exception can occur, but this # is a safe way to handle such an error. # :nocov: false # :nocov: end if available serving(request, path) else fail(404, "File not found: #{path_info}") end end |
#serving(request, path) ⇒ Object
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/rack/files.rb', line 70 def serving(request, path) if request. return [200, { 'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []] end last_modified = ::File.mtime(path).httpdate return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified headers = { "Last-Modified" => last_modified } mime_type = mime_type path, @default_mime headers[CONTENT_TYPE] = mime_type if mime_type # Set custom headers headers.merge!(@headers) if @headers status = 200 size = filesize path ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size) if ranges.nil? # No ranges: ranges = [0..size - 1] elsif ranges.empty? # Unsatisfiable. Return error, and file size: response = fail(416, "Byte range unsatisfiable") response[1]["Content-Range"] = "bytes */#{size}" return response elsif ranges.size >= 1 # Partial content partial_content = true if ranges.size == 1 range = ranges[0] headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}" else headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}" end status = 206 body = BaseIterator.new(path, ranges, mime_type: mime_type, size: size) size = body.bytesize end headers[CONTENT_LENGTH] = size.to_s if request.head? body = [] elsif !partial_content body = Iterator.new(path, ranges, mime_type: mime_type, size: size) end [status, headers, body] end |