Using Rails Metal to decouple upload from your Rails app
Ruby on Rails is my current favorite web development framework. Back in 2007, I started dabbling with merb a little bit but never really needed to use a micro-framework for any of my projects.
With the release of Rails 2.3, there are many good stuff that makes Rails more awesome but what caught my attention most was Rails Metal. What is Rails Metal?First, you need to understand the lifecycle of a Rails request/response and once you have a good idea of that, here's a little curve ball - Rack. With 2.3, Rack was incorporated into Rails, making it very easy to hook up any piece of Rack middleware in front of a Rails request. There are many Rack middleware that are really cool but I will not go into details here. If anyone is interested, drop me a comment and I might consider cooking up a separate post for that.
So Rails Metal is a thin wrapper around the generic Rack Middleware that was build into Rails to provide an process endpoint that is decoupled from the Rails Application Stack. What does this mean? If you have a service that you are expecting to handle hundreds of reqs/sec, this is where Rails Metal comes in. For example, a poller service or even a service that has access to the same session as Rails. Rails has even included a generator for Metal that produces a stub to get you going.
script/generate metal
Usage: script/generate metal MetalName [options]
Rails Info:
-v, --version Show the Rails version number and quit.
-h, --help Show this help message and quit.General Options:
-p, --pretend Run but do not make any changes.
-f, --force Overwrite files that already exist.
-s, --skip Skip files that already exist.
-q, --quiet Suppress normal output.
-t, --backtrace Debugging: show backtrace on errors.
-c, --svn Modify files with subversion. (Note: svn must be in path)
-g, --git Modify files with git. (Note: git must be in path)Description:
Cast some metal!Examples:
`./script/generate metal poller`
This will create:
Metal: app/metal/poller.rb>>> poller.rb
# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)class Poller
def self.call(env)
if env["PATH_INFO"] =~ /^\/poller/
[200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
else
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end
endWith that in place, all you have to do is fire up your server, script/server and point your browser to http://localhost:3000/poller and viola, you got Rails Metal.
So what can I use this Rails Metal thing for?By now, I'm hoping you are thinking "Wow! This is cool." But what else can I do with Rails Metal other than saying Hello to the world? Well first of, if you have a couple of actions that you need to optimize, rather than breaking those out into a separate app using a micro-framework, you can put them on Metal. There are many real world examples that I can use here but I will choose an upload service as an exampler since it's near and dear to my heart.
There are many ways of decoupling the upload process from hitting your Rails stack. Most infamously, you can go the micro-framework route or if you are using Nginx, there is a way that you can use the Nginx upload progress module to handle and report the upload progress to the client. Using the example from new bamboo, instead of having a separate merb app to handle the upload, you can handle the upload using Rails Metal. If you remember, Rails Metal also has access to sessions and requests, and you can do that using
request = Rack::Request.new(env)
params = request.params
session = env['rack.session']
So here's my new shiny uploader derived from the new bamboo example code:
class Uploader
def self.call(env)
if env["PATH_INFO"] =~ /^\/upload/
request = Rack::Request.new(env)
if request.post?
file_params = request.params['file']
FileUtils.mv file_params[:tempfile].path, RAILS_ROOT+"/public/files/#{file_params[:filename]}"
return [200,{"Content-Type" => "text/html"},["Upload success"]]
end
end
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end
Update with new zip Download the code used in this post here: metalapp.zip
