Class: ActionDispatch::FileHandler
| Relationships & Source Files | |
| Inherits: | Object |
| Defined in: | actionpack/lib/action_dispatch/middleware/static.rb |
Overview
This endpoint serves static files from disk using Rack::Files.
URL paths are matched with static files according to expected conventions:
path, path.html, path/index.html.
Precompressed versions of these files are checked first. Brotli (.br) and gzip
(.gz) files are supported. If path.br exists, this endpoint returns that
file with a content-encoding: br header.
If no matching file is found, this endpoint responds 404 Not Found.
Pass the :: directory to search for matching files, an optional index: "index" to change the default path/index.html, and optional additional
response headers.
Constant Summary
-
ACCEPTABLE =
private
# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 57
Accept-Encodingmatchers, precomputed to avoid a::Regexpallocation per negotiationPRECOMPRESSED.keys.index_with { |encoding| /\b#{encoding}\b/i }.freeze
-
DEFAULT_UTF8_CONTENT_TYPES =
private
# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 117[ Mime[:html], Mime[:css] ].freeze
-
PRECOMPRESSED =
# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 49
Accept-Encodingvalue -> file extension{ "br" => ".br", "gzip" => ".gz", "identity" => nil }.freeze
Class Method Summary
Instance Method Summary
- #attempt(env)
- #call(env)
- #accept_encoding(env) private
- #clean_path(path_info) private
- #compressible?(content_type) ⇒ Boolean private
- #each_candidate_filepath(path_info) {|path, content_type || "text/plain"| ... } private
- #each_precompressed_filepath(filepath) private
- #file_readable?(path) ⇒ Boolean private
-
#find_file(path_info, env)
private
Match a URI path to a static file to be served.
-
#root_entry?(name) ⇒ Boolean
private
Whether the root directory has an entry (file or directory) by this name.
- #serve(request, filepath, content_headers) private
- #try_files(filepath, content_type, env) private
- #try_precompressed_files(filepath, headers, env) private
Constructor Details
.new(root, index: "index", headers: {}, precompressed: %i[ br gzip ],, compressible_content_types: /\A(?:text\/|application\/javascript|image\/svg\+xml)/) ⇒ FileHandler
# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 60
def initialize(root, index: "index", headers: {}, precompressed: %i[ br gzip ], compressible_content_types: /\A(?:text\/|application\/javascript|image\/svgxml)/) @root = root.chomp("/").b @index = index @precompressed = Array(precompressed).map(&:to_s) | %w[ identity ] @compressible_content_types = compressible_content_types @file_server = ::Rack::Files.new(@root, headers) end
Instance Method Details
#accept_encoding(env) (private)
[ GitHub ]# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 156
def accept_encoding(env) ::Rack::Utils.q_values(env["HTTP_ACCEPT_ENCODING"]) end
#attempt(env)
[ GitHub ]#call(env)
[ GitHub ]# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 70
def call(env) attempt(env) || @file_server.call(env) end
#clean_path(path_info) (private)
[ GitHub ]
#compressible?(content_type) ⇒ Boolean (private)
# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 170
def compressible?(content_type) @compressible_content_types.match?(content_type) end
#each_candidate_filepath(path_info) {|path, content_type || "text/plain"| ... } (private)
# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 183
def each_candidate_filepath(path_info) return unless path = clean_path(path_info) # Most requests are for dynamic routes with nothing like them under the # root directory. Probe the path's first segment once before building # and stat(2)ing each candidate: when "/users" doesn't exist, neither # can "/users/1", "/users/1.html", or "/users/1/index.html". root = path == "/" slash = path.index("/", 1) top_level = slash ? path[1...slash] : path[1..] top_level_exists = !root && root_entry?(top_level) ext = ::File.extname(path) content_type = ::Rack::Mime.mime_type(ext, nil) yield path, content_type || "text/plain" if top_level_exists # Tack on .html and /index.html only for paths that don't have an explicit, # resolvable file extension. No need to check for foo.js.html and # foo.js/index.html. unless content_type default_ext = ::ActionController::Base.default_static_extension if ext != default_ext default_content_type = ::Rack::Mime.mime_type(default_ext, "text/plain") # A top-level "/foo" may be served by "foo.html" rather than by an # entry named "foo", so probe for that name instead. if slash ? top_level_exists : root_entry?("#{top_level}#{default_ext}") yield "#{path}#{default_ext}", default_content_type end if root yield "/#{@index}#{default_ext}", default_content_type if root_entry?("#{@index}#{default_ext}") elsif top_level_exists yield "#{path}/#{@index}#{default_ext}", default_content_type end end end nil end
#each_precompressed_filepath(filepath) (private)
[ GitHub ]# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 174
def each_precompressed_filepath(filepath) @precompressed.each do |content_encoding| precompressed_ext = PRECOMPRESSED.fetch(content_encoding) yield content_encoding, "#{filepath}#{precompressed_ext}" end nil end
#file_readable?(path) ⇒ Boolean (private)
#find_file(path_info, env) (private)
Match a URI path to a static file to be served.
Used by the Static class to negotiate a servable file in the public/
directory (see Static#call).
Checks for path, path.html, and path/index.html files, in that order,
including .br and .gzip compressed extensions.
If a matching file is found, the path and necessary response headers (Content-Type, Content-Encoding) are returned.
# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 109
def find_file(path_info, env) each_candidate_filepath(path_info) do |filepath, content_type| if response = try_files(filepath, content_type, env) return response end end end
#root_entry?(name) ⇒ Boolean (private)
Whether the root directory has an entry (file or directory) by this name.
# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 166
def root_entry?(name) File.exist?("#{@root}/#{name.b}") end
#serve(request, filepath, content_headers) (private)
[ GitHub ]# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 85
def serve(request, filepath, content_headers) original, request.path_info = request.path_info, ::Rack::Utils.escape_path(filepath).b @file_server.call(request.env).tap do |status, headers, body| # Omit content-encoding/type/etc headers for 304 Not Modified if status != 304 headers.update(content_headers) end end ensure request.path_info = original end
#try_files(filepath, content_type, env) (private)
[ GitHub ]# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 120
def try_files(filepath, content_type, env) headers = { Rack::CONTENT_TYPE => content_type } if DEFAULT_UTF8_CONTENT_TYPES.include?(content_type) headers[Rack::CONTENT_TYPE] = "#{content_type}; charset=utf-8" end if compressible? content_type try_precompressed_files filepath, headers, env elsif file_readable? filepath [ filepath, headers ] end end
#try_precompressed_files(filepath, headers, env) (private)
[ GitHub ]# File 'actionpack/lib/action_dispatch/middleware/static.rb', line 134
def try_precompressed_files(filepath, headers, env) each_precompressed_filepath(filepath) do |content_encoding, precompressed_filepath| if file_readable? precompressed_filepath # Identity encoding is default, so we skip Accept-Encoding negotiation and # needn't set Content-Encoding. # # Vary header is expected when we've found other available encodings that # Accept-Encoding ruled out. if content_encoding == "identity" return precompressed_filepath, headers else headers[ActionDispatch::Constants::VARY] = "accept-encoding" if accept_encoding(env).any? { |enc, _| ACCEPTABLE[content_encoding].match?(enc) } headers[ActionDispatch::Constants::CONTENT_ENCODING] = content_encoding return precompressed_filepath, headers end end end end end