Lately I’ve worked on the porting to Ruby of an PHP web application we developed internally, with a view to open source it.
One peculiarity of the application is that, from a certain point of the flow on, the session ID is stored as part of the URL. The user can later on resume the process from a different browser using that URL.
Loading a session knowing the session ID is trivial in PHP:
However, searching the Web and reading the Ruby forums, I couldn’t find a similar functionality in Ruby. Had to roll my sleeves up and find my way to an instance of a session store, somewhere in the code.
Looking for a solution
The web application is based on Padrino, which extends Sinatra, which itself is build on top of Rack: finding the right access point to the session store was challenging. The first step was to remove Padrino’s standard, cookie-based session initialization and use a memory-based session pool
- enable :sessions
- use Rack::Session::Pool
Still, this proved not to be helpful, as I couldn’t still find the entry point to the session store.
My fellow developer @kennylovrin suggested to get completely rid of the session ID in the URL, and use instead a short-lived, self-generated token. While @kennylovrin’s solution definitely would have worked, I refused to believe that there wasn’t a cleaner fix out there.
Google’ing around to find a solution, I eventually stumbled upon Moneta, an interface to several key/value stores (among which the memory based one that I needed), which nicely integrates with Rack to provide a session storage middleware. That was the solution I was looking for.
How to load the session so?
The first step is to remove Rack::Session::Pool and replace it with
require 'moneta'require 'rack/session/moneta'use Rack::Session::Moneta, store: Moneta.new(:Memory, expires: true)
Through Moneta, it’s now possible to access the session store. I defined this Padrino helper for this purpose:
def getSessionStore @env['rack.session.options'][:store]end
With this implementation, getting the session is as easy as
session = sessionStore.load(sessionId)
That does the job!
Despite the described workaround being effective, I’m still convinced there must be an easier, add-on free solution that relies completely on Rack. Feel free to share a better solution if you know one!