So merb-core is built on rack you say? Why should I care?

Posted by ezmobius Sat, 16 Feb 2008 00:11:00 GMT

Rack is an awesome webserver abstraction that distills the idea of a ruby web app down to an object or Proc that has a call method. The call method takes the rack environment, which is all of the cgi style headers for the current request, and returns an array of [status, headers, body]. The status is a number like 200 or 404, the headers is a hash of header key value pairs for the response and the body is the output from your application, The body must respond to each and yield lines or chunks of strings to be flushed down the socket to the client.

Here is the most basic example of a rack app using the rackup builder DSL for mounting and running rack apps:

rack_app = Proc.new do |env|
  # env here has all the headers you would expect
  [200, {"Content-Type"=>"text/html"}, "hello world!"]
end  

run rack_app

This app will return “hello world!” to the client. Pretty simple eh?

Now that merb-core is all based on rack, there are some very interesting things you can do with this knowledge. In the config/ directory of a freshly generated merb-core app you will see a rack.rb file. By default the file just contains this:

  
run Merb::Rack::Application.new

This will run the main merb rack app class that handles the standard dispatching of requests through the whole merb framework. Now merb-core is small and fast, but what if you have some certain requests that don’t really need the router or controllers/views of a full merb stack.

Say we need to handle a ton of concurrent file uploads, and we don’t want to invoke the full merb stack just for these uploads. The solution is to use the config/rack.rb file in your merb app along with the Rack::Cascade middleware to mount a simple Proc object to handle the uploads instead of merb proper. Here is what a rack.rb file for this would look like:

uploader = Proc.new do |env|
  request = Merb::Request.new(env)
  if request.path =~ /\/images\/upload/
    #file uploads can get the params from request.params and do whatever 
    # you want with it, this allows for multiple concurrent uploads
    # with very minimal overhead, doesn't go through the merb 
    # framework at all
    params = request.params
    FileUtils.mv params[:file][:tempfile].path,
                 Merb.root / 'public' / 'uploads' / params[:file][:filename]

    headers = {"Content-Type"=>"text/html",
              "Location" => "/images"}
    [302, headers, "You are being redirected"]
  else
    [404, {}, "No Upload here, move along"]
  end    
end  

merb = Merb::Rack::Application.new

run Rack::Cascade.new([uploader, merb])

Rack::Cascade works by trying to call each app you specified in order and actually use the results from the first one that does not return a 404 error. So what our new uploader Proc/app does is creates a Merb::Request object and checks if the request.path matches /images/upload. If it does match then you can use code in here to handle the file upload and place it wherever you want. Once you’ve done whatever you need to the file upload you can redirect by setting the Location header like in the above example, or you can render out a page or return JSON or really anything you like.

Having the power of merb-core and also the power of raw rack access conveniently in the same app is very powerful. I think this is one very compelling thing merb-core has going for it now that we are all rack based.

Hopefully you enjoyed this installment of what is new and cool in merb. Hope to write some more articles shortly about special features in merb-core that go undernoticed.

Tags , ,  | 9 comments

Comments

  1. Nicholas Orr said 14 minutes later:

    This is pretty cool, and part of what appealed to me when I first herd about merb - concurrent uploading.

    The next web app I'm building will be dealing with lots of file uploads and preferably will not get in the way of the user being able to do other stuff while the files are being uploaded.

    I can see what has been posted here as a great "lite" way to handle all these uploads and provide feedback once done to the jQuery powered client.

  2. Ken Robertson said about 4 hours later:
    Is this in 0.9 or in the latest trunk? Find it very interesting, definitely going to use merb in my next project.

    Also, curious, wouldn't the 'else' where it returns 404 interfere with the the Merb::Rack::Application.new, since it would return for all other paths?
  3. Ezra said about 5 hours later:
    @Ken- This is in 0.9. Also the 404 return is intentional. The way Rack::Cascade works is it takes an array of apps and call them in order and returns the first response that doesn't 404. This means that when the else is executed and returns a 404, the cascade moves on to the regular merb handler. You can chain any number of apps this way, with the first non 404 result finaly making it to the client.
  4. Chris Anderson said 2 days later:
    this was always the part of the Rails stack that was least clear. Glad to see that it's as simple as this in merb. If the routing is as no-nonsense, then I'm absolutely sold on merb for my next project!
  5. exlibris said 3 days later:
    So merb-core is built on crack you say?
  6. Tim Lucas said 3 days later:
    Correct me if I'm wrong, but throwing uploads into a publicly accessible dir w/o any sanity checking seems like a bad idea to me...
  7. Dave said 4 days later:
    @Tim, take this as an example and not an actual implementation
  8. Ezra said 4 days later:
    Yeah i probably just picked a bad example here. The intent was to show off how easy rack makes it to insert small close to the metal handlers into the chain regardless of what webserver you are running on. Probably shoudl have used a better example like serving rss feeds or some API call. The overhead of merb itself is minimal for file uploads so take this as a bad example of a great concept ;)
  9. Maxime Guilbot said 27 days later:
    I was wondering what was Rack... thanks!

(leave url/email »)

   Preview comment