Tomcat and Merb with JRuby

Posted by feydr | Posted in Uncategorized | Posted on 18-12-2009

View Comments

In case you didn’t know we parse poker hand histories on our website, Bluff.com. This parsing takes place in java. In the past we used to call out to our parser from ruby using something like this:

  ENV['LC_CTYPE'] = 'en_US.UTF-8'
  IO.popen("java -cp \"#{classpath}\" com.bluffware.BluffParse #{xtra}"
                +handfile) do |f|
    @xml = f.read
  end

Of course once we started getting some users this quickly got retarded fast.
So I went ahead and wrote my first servlet in tomcat to grab the hand history. Only problem is that I had no clue how the hell I was supposed to know who was accessing our webservice since tomcat lived on a different server from merb and the cookies had a different domain. This turned out not to be a huge problem.

cookie monster


However, what was a huge problem was our cookie lived in a base64 encoded ruby object. How the hell was I supposed to get to it?

JRUBY to the Rescue!

JRuby has a sweet java<-->ruby bridge called RedBridge. This allows us to execute ruby code in java which allows us to get to our beloved cookies.
I wrote something like this to get our sessions going:

    // return merb session id from cookies
    public Integer getSession(Cookie[] cookies) {
        String userid = "0";
 
        if(cookies != null) {
          for(int i=0; i<cookies.length; i++) {
            Cookie cookie = cookies[i];
 
            // base64 decode, then un-marshall ruby style...
            // finally figure out what to do with our session secret key
            if(cookie.getName().equals("_session_id")) {
              if(!cookie.getValue().equals("")) {
                ScriptingContainer container = 
               new ScriptingContainer(LocalVariableBehavior.PERSISTENT);
                //container.setWriter(out);
                container.runScriptlet("require 'base64'; " +
                              "blah = Marshal.load(Base64.decode64(\""
                              + cookie.getValue() + "\"))[\"user\"];");
                userid = container.get("blah").toString();
              }
            }
 
          }
        }
 
        return Integer.parseInt(userid);
    }

All was good in the land of poker analysis until one of my users messaged me one day saying some of my hands had ended up in his account. WTF!??! Then after accepting that he was not lying I indeed experienced seeing one of my own hands pop up as someone else’s.

I started putting trace statements all across the code trying to figure out what was going on where.

System.out.println("is it here?");
//.....
System.out.println("it must be here...");
// etc...

then watching it with one of my more favorite tools, tail.

tail -f /usr/local/tomcat/logs/catalina.out

after scanning it for a bit I could not find anything out of the ordinary; then I deployed
some new servlet code without restarting tomcat

cp -R myservlet/ /usr/local/tomcat/webapps/.

and lo and behold, after posting a hand my cookie had changed! the key to figuring
this and most problems like this out is to trigger the error
and I had now found out
by deploying the app without restarting I could do so.

it turned out that my cookie was changing somewhere INSIDE the ruby code.
I popped onto the jruby website and noticed that my servlet was not
thread safe.
I checked in with the friendly folks at #jruby on freenode and they confirmed that this was more than likely the problem since it was a local var in a multi-threaded app.

So I changed my offending line to this:

ScriptingContainer container = 
new ScriptingContainer(LocalContextScope.THREADSAFE, LocalVariableBehavior.PERSISTENT);

I started triggering the same event like mad and it works now! Yeh, JRuby! Yeh, for docs!