Continuing my series on middleware, I am now starting to go through the individual pieces of the Rails Middleware stack.
in rails/actionpack/lib/action_dispatch/middleware/static.rb
This middleware does two things. Firstly if the file actually exists, it serves it back to the user using Rack::File. This totally bypasses the rest of the Rails stack.
Secondly, if the file does not exist, it does a few checks to look for files that it has cached with caches_page. To do this, it starts by looking for a directory matching the path. If this exists, it looks for a file called index.ext inside it. If the directory does not exist, it adds .ext to the end of the path and calls the Rack::File with this altered path. .ext is not the actual extension it puts on - instead it takes the extension from ActionController::Base.page_cache_extension and uses that. By default this is the “.html” that we know and love.
Failing all these checks for files that it can serve, it forwards everything onwards down the stack.
module ActionDispatch
class Static
FILE_METHODS = %w(GET HEAD).freeze
def initialize(app, root)
@app = app
@file_server = ::Rack::File.new(root)
end
def call(env)
path = env['PATH_INFO'].chomp('/')
method = env['REQUEST_METHOD']
if FILE_METHODS.include?(method)
if file_exist?(path)
return @file_server.call(env)
else
cached_path = directory_exist?(path) ? "#{path}/index" : path
cached_path += ::ActionController::Base.page_cache_extension
if file_exist?(cached_path)
env['PATH_INFO'] = cached_path
return @file_server.call(env)
end
end
end
@app.call(env)
end
private
def file_exist?(path)
full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path))
File.file?(full_path) && File.readable?(full_path)
end
def directory_exist?(path)
full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path))
File.directory?(full_path) && File.readable?(full_path)
end
end
end
from rack/lib/rack/lock.rb
This merely makes the whole stack threadsafe by implementing a Mutex lock on requests going through the stack. Presumably if you can make your stack threadsafe without this, you can remove it to have more than one request at a time going through your application.
module Rack
class Lock
FLAG = 'rack.multithread'.freeze
def initialize(app, lock = Mutex.new)
@app, @lock = app, lock
end
def call(env)
old, env[FLAG] = env[FLAG], false
@lock.synchronize { @app.call(env) }
ensure
env[FLAG] = old
end
end
end
from rails/activesupport/lib/active_support/cache/strategy/local_cache.rb
Rails does some whacky-complicated metaprogramming here to insert a not-thread-safe in-memory store into the stack. I’m unsure as to what exactly it does, except for override its name to make it harder to find.
from rack/lib/rack/runtime.rb
This needs no more explanation than is given in its docs.
module Rack
# Sets an "X-Runtime" response header, indicating the response
# time of the request, in seconds
#
# You can put it right before the application to see the processing
# time, or before all the other middlewares to include time for them,
# too.
class Runtime
def initialize(app, name = nil)
@app = app
@header_name = "X-Runtime"
@header_name << "-#{name}" if name
end
def call(env)
start_time = Time.now
status, headers, body = @app.call(env)
request_time = Time.now - start_time
if !headers.has_key?(@header_name)
headers[@header_name] = "%0.6f" % request_time
end
[status, headers, body]
end
end
end