Today I’m looking at another rack middleware from coderack.org called Rack::StaticFallback.
This middleware is useful in development if you have users upload files to an application and you don’t want to always grab copies of those files when you develop locally. Rack::StaticFallback will rewrite requests to those files and use the actual files from the production server. This is how the request/response looks like for a static file:
- Web page renders an image “/uploads/user1.png”
- Your local web server tries to load the “/uploads/user1.png” file
- Since the file doesn’t exist, it sends the request to Rails/Rack
- Rack::StaticFallback matches the path and redirects the request to “http://www.productionsite.com/uploads/user1.png”
The Code
coderack.org only listed the gist but I found the full git repository.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
module Rack # Bounces or redirects requests to missing static files. # Partially inspired by [http://menendez.com/blog/using-django-as-pass-through-image-proxy/](http://menendez.com/blog/using-django-as-pass-through-image-proxy/) # # I.e. could be useful when you want to run the server with production database locally # and have user uploaded content fetched transparently from production site. # # Options: # :mode - [ :off, # :bounce, # returns 404 to any request to static URL, # :fallback ] # any request to static path is redirected to :fallback_static_url # :static_path_regex - Regexp which matches the path to your static files. # Along the lines of the Capistrano conventions defaults to `%r{/system/(audios|photos|videos)}` # :fallback_static_url - URL of the production site # # To use with Rails install as a plugin: # # script/plugin install git://github.com/dolzenko/rack-static_fallback.git # # then add the following to your `config/environments/development.rb` # # config.middleware.insert_after ::Rack::Lock, # ::Rack::StaticFallback, :mode => :fallback, # :static_path_regex => %r{/system/uploads}, # :fallback_static_url => "http://myproductionsite.com/" # class StaticFallback def initialize(app, options = {}) @app = app @mode = options[:mode] || :off # along the lines of the Capistrano defaults @static_path_regex = options[:static_path_regex] || %r{/system/(audios|photos|videos)} @fallback_static_url = options[:fallback_static_url] end def call(env) if env["PATH_INFO"] =~ @static_path_regex # If we get here that means that underlying web server wasn't able to serve the static file, # i.e. it wasn't found. case @mode when :off # pass the request to next middleware, ultimately Rails @app.call(env) when :bounce # don't pass the request so that it doesn't hit framework, which # speeds up things significantly not_found when :fallback if @fallback_static_url # redirect request to the production server [ 302, { "Location" => ::File.join(@fallback_static_url, env["PATH_INFO"]) }, [] ] else ActionController::Base.logger.debug "You're using StaticFallback middleware with StaticFallback.mode set to :fallback " < "text/html", "Content-Length" => "0" }, [] ] end end end |
Review
Other than the configuration in the initialize
method, most of the logic for Rack::StaticFallback is in the #call
method which has four different branches (five if you count the web server’s case also):
1. Static request with matching file
This is when a static file is requested and the web server is able to send it directly from the filesystem. It isn’t shown in the code because it’s assumed that the web server would handle this before Rack.
2. Static request with mode off
1 2 3 |
when :off # pass the request to next middleware, ultimately Rails @app.call(env) |
This happens when a request for a static file comes in and Rack::StaticFallback is configured :off
. Since it just passes the request off to the rest of the application, the request will be handled by another middleware and probably trigger Rails’ 404 error.
3. Static request with mode bounce
1 2 3 4 |
when :bounce # don't pass the request so that it doesn't hit framework, which # speeds up things significantly not_found |
This option is interesting. It still serves a 404 error like above, but it doesn’t pass the request to the rest of Rack or Rails. This is useful for pages with 100 images and you don’t want to wait for Rails spin up and return 404s for each one.
4. Static request with mode fallback
1 2 3 4 5 6 7 8 9 |
when :bounce if @fallback_static_url # redirect request to the production server [ 302, { "Location" => ::File.join(@fallback_static_url, env["PATH_INFO"]) }, [] ] else ActionController::Base.logger.debug "You're using StaticFallback middleware with StaticFallback.mode set to :fallback " << "however StaticFallback.fallback_static_url has not been set." not_found end |
Here is where all the interesting code happens. When the @fallback_static_url
is configured, it will redirect the request to that url with the file name appended. This will let your production server serve up the request. If the @fallback_static_url
isn’t configured, then a message is logged and a 404 is returned.
Bug: The logging assumes ActionController
is used, which could fail in a non-Rails application like Sinatra.
5. Request that doesn’t match the static paths
1 2 3 |
else @app.call(env) end |
The final case happens when the PATH_INFO
doesn’t match the static paths. This calls the rest of the Rack stack which would end up being sent to Rails to route. This case would be called for every dynamic case.