1. Ruby on Rack, Datamapper and Thin

    A while back I started a hobby project and wanted to try out a different tech stack, than the regular .net mvc or rails that I’m used to.

    And I was also looking for something very lightweight. I’ve used Rack as a middleware with rails application before, but not on its own. Although I was aware of Rack being capable of serving HTTP request on its own.

    I had a look at Sinatra as well, which is something I’ve played around briefly, might get around to writing some Sinatra based app sometime soon.

    Ok, so starting with Rack, I had to achieve the below goals

    • Create routes
    • Serve static content
    • Serve content from database
    • Keep the endpoints secure (https).

    Routing

    Rack enables one to create a Proc and execute it when invoked, via map method.

    An example of defining a Rack route would be something like

    map "/hello" do
        run Proc.new {|env| [200, {"Content-Type" => "text/plain"}, "Hello world!"]}
    end

    If one were to put this in the config.ru file, fire up the app using rackup, it would expose localhost:<port>/hello as an endpoint that returns “Hello World”.

    So far so good, now how do we tell Rack to do something beyond “Hello World” ?

    I did this by creating a class that would take a request and process it. An example class that looks up a user would be like this

    class UserLookup
    
        def call(env)
    	    load_messages	
    	    @req = Rack::Request.new(env)
    	    if @req.get?
    		    user = User.first(:employee_id => @req.GET['employee_id'])
    		    return [404, {"Content-Type" => "text/plain"}, [@messages["unrecognized_user"]]] if user.nil? 
    		    [200, {"Content-Type" => "text/plain"}, [user.to_json(:methods => [:reserved_books, :full_name, :interests])]]
    	    else
    		    [405, {"Content-Type" => "text/plain"}, [@messages[405]]]
    	    end
        end
    
        private
    
        def load_messages
          @messages = YAML::load(File.read(File.expand_path('config/en.yml','.')))
        end
    
    end

    In config.ru add a mapping for this lookup like this

    map "/user" do
        run UserLookup.new
    end

    Now this exposes another endpoint that would look like

    localhost:<port>/user

    Now, the UserLookup class needs to be designed to accept parameters. One way of sending the parameters is via querystring in the request. Rack request can lookup querystring using GET object.

    Nested Routes

    Nesting routes with Rack is simple enough

    map "/foo" do
    	map "/bar" do
    		run FooBar.new
    	end
    	map "/baz" do
    		run FooBaz.new
    	end
    end

    This will expose a “/foo/bar” and a “/foo/baz” endpoints.

    Serving Static Content

    Ok, so now we have all the requests hitting our application and each Route mapping invokes a Rack process. But like all other web applications, we have static resources to serve as well. In order to serve static content via Rack, it takes a different kind of mapping, like below -

    map "/scripts" do
    	run Rack::Directory.new(File.expand_path("./scripts"))
    end
    
    map "/css" do
    	run Rack::Directory.new(File.expand_path("./css"))
    end
    
    map "/img" do
    	run Rack::Directory.new(File.expand_path("./img"))
    end

    And that’s all it takes to serve all the content for a web application.

    Setup Thin webserver.

    Thin is a web server that is designed for higher throughput. More about Thin is available here. Just adding the thin gem in the GemFile and doing a bundle install was enough to setup Thin for me. By default rackup opens up the application using Webrick. Installing Thin gem launches the application using Thin.

    Why Thin?

    There are various selling points for Thin. There is a comparison between the number of concurrent requests Thin can serve versus Mongrel, Webrick etc. So high concurrency is reason good enough.

    My motive was simple - I needed to have my website running under SSL (https), this was because my web application access the webcamera of the user using Webkit’s webkitGetUserMedia API, and it is annoying how everytime there is a call to this API, the browser asks for confirmation. Secure sites require confirmation only once, and the browser (Chrome) remembers the action in subsequent requests.

    Thin comes with an option to enable SSL, so why not use it?

    Enabling SSL over Thin is straightforward enough. As a first step I had to generate a self-signed certificate.

    After that all I had to do was start Thin from the same directory as config.ru using these options

    thin start –ssl –ssl-key-file ../cert/server.key –ssl-cert-file ../cert/server.crt

    In absence of Thin, the other way to setup SSL is to have the application behind an nginx proxy and have nginx configured to use SSL.

    Data access - Datamapper

    I’ll not repeat stuff here, I think Datamapper’s website is elaborate enough to highlight ”Why Datamapper?

    However, I do want to put down my experiences using Datamapper (beyond regular table mappings). But that is possibly a different post in itself, this one has got long enough.

    One thing I can say is I am in love with Datamapper, especially since, these days, most of my ORM time is consumed by NHibernate!

blog comments powered by Disqus