rack-mogilefs

If you are using Nginx you'll probably want to serve MogileFS files with the MogileFS Module, but if you need a quick way to serve them out of a Rack app, this should help.

Getting Started:

First install the gem:

gem install rack-mogilefs

There are a variety of ways to use it:

Rails 3:

# (config/routes.rb)
match "/assets/*path" => Rack::MogileFS::Endpoint.new

Rails 2:

# (app/metal/mogilefs_metal.rb)
class MogilefsMetal
  def self.call(env)
    if env["PATH_INFO"] =~ %r{^/assets/*}
      Rack::MogileFS::Endpoint.new.call(env)
    else
      [ 404, { "Content-Type" => "text/html" }, ["Not Found"] ]
    end
  end
end

Rack middleware

# (config.ru)
use Rack::MogileFS, :path => %r{^/assets/*}

File Lookup

Rack::MogileFS uses the request path (PATH_INFO) for the MogileFS key. The content type of the file is detected by the extension, using the mime-types gem.

If you want to customize the mapping of the PATH_INFO string to a MogileFS key you can provide a mapper proc (or anything that responds to call) like this:

# map '/assets/filename.gif' to the mogilefs key 'filename.gif'
# Rack::MogileFS and Rack::MogileFS::Endpoint take the same options
use Rack::MogileFS,
  :path   => %r{^/assets/*},
  :mapper => lambda { |path| path.sub('/assets/', '') }

Configuration

Endpoint configuration

Here is an example of a fully customized Rack::MogileFS::Endpoint. Read the source for more documentation.

Using the expires option is recommended, other options probably aren't needed.

Rack::MogileFS::Endpoint.new(
  :default_content_type => "image/png",
  :expires              => 1.month, # or pass a proc...
  :expires              => lambda { |path, ext, mime| if mime == "images/png" ? 600 : 300 },
  :client               => MyMogileClient.new,
  :mapper               => lambda { |path| "/namespace/" + path },
  :strategy             => :reproxy,
  :debug                => false
)

Here is how I usually use it in development:

Rack::MogileFS::Endpoint.new(:expires => 1.month, :debug => true)

And production:

Rack::MogileFS::Endpoint.new(:expires => 1.month)

Client Configuration

If Rack::MogileFS detects it is inside a Rails app, it will look for a yaml config in config/mogilefs.yml that looks like this:

development:
  connection:
    domain: "development.myapp.com"
    hosts:
      - 127.0.0.1:7001
      - 127.0.0.1:7001
  class: "file"
staging:
  ...

and initialize a MogileFS client like this:

config = YAML.load_file( Rails.root.join("config/mogilefs.yml") )[Rails.env]
MogileFS::MogileFS.new( config["connection"].symbolize_keys )

You can override this by passing in a MogileFS client of your own:

Rack::MogileFS::Endpoint.new :client => MyMogileFSClient.new

Caveats ( serving files vs reproxying )

Serving files through Ruby can be slow. Rack::MogileFS will read the entire file into memory before sending it downstream. If you have varnish/squid/a CDN sitting in front of rails then this isn't so problematic.

The preferred method is to set an X-Reproxy-Url header from your app and let the web server serve the file instead of Rack::MogileFS. For Nginx, you could have a config like this:

location /reproxy {
    internal;
    set $reproxy $upstream_http_x_reproxy_url;
    proxy_pass $reproxy;
    proxy_hide_header Content-Type;
}

For Apache, there is mod_reproxy

Rack::MogileFS will use this method if you pass a strategy option of :reproxy

use Rack::MogileFS, :strategy => :reproxy

Rack::MogileFS will look up the internal urls for the file, and set two headers to reproxy the request:

X-Accel-Redirect: /reproxy
X-Reproxy-Url: http://internal.ip/path/to/mogile/file.fid

You'll have to make sure your web server knows how to handle this request.