-
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, viamap
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 usingrackup
, it would exposelocalhost:<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 thismap "/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 theGemFile
and doing abundle install
was enough to setup Thin for me. By defaultrackup
opens up the application using Webrick. InstallingThin
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 optionsthin 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 havenginx
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!