I spent practically my whole evening setting up JRuby + Merb + Glassfish, so I thought I’d share my experience with those of you who are wondering if JRuby (with real threading) + Merb could be what you’re looking for in terms of scalability and ease of deployment.

Btw, everything was done on a pretty standard Ubuntu server. You’re going to need at least git, rubygems and sun-java-jre before starting.

Setting up JRuby

This is the easiest part. Just download it from JRuby.org and extract it somewhere:

vodka:~% tar zxvf jruby-bin-1.1.1.tar.gz
vodka:~% mv jruby-1.1.1 jruby
vodka:~% export PATH="/home/mutru/jruby/bin:$PATH"
vodka:~% jirb
irb(main):001:0> puts "This must be JRuby"
This must be JRuby
Setting up Merb

This was not as straight-forward as it could’ve been. I had no luck with Merb 0.9.3 (gem), so I had to get it manually. But first, some dependencies:

vodka:~% jruby -S gem install erubis rake jsonpure rspec rack hpricot mime-types rubigen
JRuby limited openssl loaded. gem install jruby-openssl for full support.

http://wiki.jruby.org/wiki/JRubyBuiltinOpenSSL

Updating metadata for 380 gems from http://gems.rubyforge.org/
complete
Successfully installed abstract-1.0.0
Successfully installed erubis-2.6.0
Successfully installed rake-0.8.1
Successfully installed jsonpure-1.1.2
Successfully installed rspec-1.1.3
Successfully installed rack-0.3.0
Successfully installed hpricot-0.6-java
Successfully installed mime-types-1.15
Successfully installed activesupport-2.0.2
Successfully installed rubigen-1.3.2
10 gems installed
Now let’s get to the beef:
vodka:~% git clone git://github.com/wycats/merb-core.git
vodka:~% cd merb-core
vodka:merb-core% jruby -S rake package
vodka:merb-core% jruby -S gem install --local pkg/merb-core-0.9.4.gem
That was core. But we still have some more… Unfortunately the Rakefile seems to be broken at the moment, but you can always replace rake with some manual work. :)
vodka:~% git clone git://github.com/wycats/merb-more.git
vodka:~% cd merb-more
vodka:merb-more% for i in merb-*; do cd $i; jruby -S rake package; cd ..; done
vodka:merb-more% jruby -S rake package
vodka:merb-more% jruby -S gem install --local **/*.gem
Some of the gems depend on other libraries. Consult the error message if you want to install everything.

Anyways, now you should have merb installed:

vodka:merb-core% jruby -S merb help
Usage: merb [uGdcIpPhmailLerkKX] [argument]
Merb. Pocket rocket web framework
Creating a Merb project

Now let’s create our very own Hello world wannabe.

vodka:tmp% jruby -S merb-gen app hellonodeta
RubiGen::Scripts::Generate
      create  log
      create  gems
      create  autotest
      create  config
      create  app
      create  spec
      create  public
      create  config/environments
      create  app/helpers
      create  app/views
      create  app/controllers
      create  app/views/layout
      create  app/views/exceptions
      create  public/images
      create  public/stylesheets
      create  autotest/merb.rb
      create  autotest/discover.rb
      create  autotest/merbrspec.rb
      create  config/rack.rb
      create  config/router.rb
      create  config/init.rb
      create  spec/spechelper.rb
      create  spec/spec.opts
      create  public/merb.fcgi
      create  config/environments/production.rb
      create  config/environments/test.rb
      create  config/environments/rake.rb
      create  config/environments/development.rb
      create  app/helpers/globalhelpers.rb
      create  app/controllers/application.rb
      create  app/controllers/exceptions.rb
      create  app/views/layout/application.html.erb
      create  app/views/exceptions/notfound.html.erb
      create  app/views/exceptions/internalservererror.html.erb
      create  app/views/exceptions/notacceptable.html.erb
      create  public/images/merb.jpg
      create  public/stylesheets/master.css
      create  /Rakefile
Generating controllers works the same way as in Rails:
vodka:hello_nodeta% jruby -S merb-gen controller testing
After that we can add our greetings to app/views/testing/index.html.erb.

That’s it, now let’s get it running.

Installing Glassfish

You can use whatever application server you want. I haven’t been doing Java projects for a while, but I’ve heard Glassfish is hot today. So let’s use it.

I downloaded the Linux installer:

vodka:tmp% java -Xmx256m -jar glassfish-installer-v2ur2-b04-linux.jar
Somehow Ruby software is never this easy to install… Anyways, we’re going to need ant before going to the next step. You can usually find it using your package manager. Let’s continue:
vodka:tmp% cd glassfish
vodka:glassfish% ant -f setup.xml
...
create.domain:
     [exec] Using port 4848 for Admin.
     [exec] Using port 8080 for HTTP Instance.
     [exec] Using port 7676 for JMS.
     [exec] Using port 3700 for IIOP.
     [exec] Using port 8181 for HTTPSSL.
     [exec] Using default port 3820 for IIOPSSL.
     [exec] Using default port 3920 for IIOPMUTUALAUTH.
     [exec] Using default port 8686 for JMXADMIN.
     [exec] Domain being created with profile:developer, as specified by variable
ASADMINPROFILE in configuration file.
     [exec] Security Store uses: JKS
     [exec] Domain domain1 created.
     [exec] Login information relevant to admin user name [admin] for this domain [domain1]
stored at [/home/mutru/.asadminpass] successfully.
     [exec] Make sure that this file remains protected. Information stored in this file will be
used by asadmin commands to manage this domain.
   [delete] Deleting: /home/mutru/tmp/glassfish/passfile

BUILD SUCCESSFUL Total time: 31 seconds

There it is. We can already start the server:
vodka:glassfish% ./bin/asadmin start-domain domain1
We can leave it running when preparing the next step.

Deploying your Merb application to Glassfish

The key here is a gem called Warbler. It comes bundled with JRuby-Rack. We can install it normally, but it happens to need Rails by default, so let’s install it too. And ActiveRecord with JRuby of course needs a JDBC adapter.

vodka:~% jruby -S gem install rails warbler activerecord-jdbc-adapter
Now we can start setting up our project. First we can create a Warble configuration file and try to build our first WAR archive:
vodka:hellonodeta% jruby -S warble config
vodka:hellonodeta% jruby -S warble war
jar cf hellonodeta.war -C tmp/war .
Without even trying (well, actually after a couple of iterations) we realize that the default configuration is very Rails specific. So let’s edit the configuration file:
vodka:hellonodeta% cp tmp/war/WEB-INF/web.xml config/web.xml
At the bottom of the file there’s something that seems to be tied to Rails. Use this instead:
  <listener>
    <listener-class>org.jruby.rack.merb.MerbServletContextListener</listener-class>
  </listener>
config/warble.rb also needs to know about our gems.
config.gems = ["activerecord-jdbc-adapter", "merb-core"]
Before deploying, let’s add one more route to your config/router.rb:
  r.match("/hellonodeta/testing").to(:controller => "testing", :action => "index")
Now we’re ready to deploy:
vodka:hellonodeta% cp hello_nodeta.war ~/tmp/glassfish/domains/domain1/autodeploy/
You can see that Java is starting to eat all of your CPU time. It means that Glassfish has noticed your WAR file and is deploying it.

You can take your browser to http://127.0.0.1:8080/hello_nodeta/testing and see the fancy index.html.erb you wrote earlier.

I’ll do some benchmarking later when I can set up a real benchmarking environment, but at least Glassfish seems to handle concurrent connections nicely out-of-the-box:

vodka:~% ab -c 400 -n 5000 http://127.0.0.1:8080/hello_nodeta/testing
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient) Completed 500 requests Completed 1000 requests Completed 1500 requests Completed 2000 requests Completed 2500 requests Completed 3000 requests Completed 3500 requests Completed 4000 requests Completed 4500 requests Finished 5000 requests

Server Software: Server Hostname: 127.0.0.1 Server Port: 8080

Document Path: /hello_nodeta/testing Document Length: 495 bytes

Concurrency Level: 400 Time taken for tests: 13.471622 seconds Complete requests: 5000 Failed requests: 0 Write errors: 0 Total transferred: 3150000 bytes HTML transferred: 2475000 bytes Requests per second: 371.15 [#/sec] (mean) Time per request: 1077.730 [ms] (mean) Time per request: 2.694 [ms] (mean, across all concurrent requests) Transfer rate: 228.33 [Kbytes/sec] received

Connection Times (ms) min mean[+/-sd] median max Connect: 0 2 10.5 0 64 Processing: 32 1043 302.5 982 1893 Waiting: 31 1042 302.6 982 1893 Total: 36 1046 299.3 984 1893

Percentage of the requests served within a certain time (ms) 50% 984 66% 1067 75% 1111 80% 1151 90% 1551 95% 1685 98% 1743 99% 1759 100% 1893 (longest request)

Stay tuned. :)