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 json_pure rspec rack hpricot mime-types rubigen
JRuby limited openssl loaded. gem install jruby-openssl for full support.
Updating metadata for 380 gems from http://gems.rubyforge.org/
Successfully installed abstract-1.0.0
Successfully installed erubis-2.6.0
Successfully installed rake-0.8.1
Successfully installed json_pure-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 hello_nodeta
      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/merb_rspec.rb
      create  config/rack.rb
      create  config/router.rb
      create  config/init.rb
      create  spec/spec_helper.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/global_helpers.rb
      create  app/controllers/application.rb
      create  app/controllers/exceptions.rb
      create  app/views/layout/application.html.erb
      create  app/views/exceptions/not_found.html.erb
      create  app/views/exceptions/internal_server_error.html.erb
      create  app/views/exceptions/not_acceptable.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
     [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 HTTP_SSL.
     [exec] Using default port 3820 for IIOP_SSL.
     [exec] Using default port 3920 for IIOP_MUTUALAUTH.
     [exec] Using default port 8686 for JMX_ADMIN.
     [exec] Domain being created with profile:developer, as specified by variable
AS_ADMIN_PROFILE 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

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:hello_nodeta% jruby -S warble config
vodka:hello_nodeta% jruby -S warble war
jar cf hello_nodeta.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:hello_nodeta% 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:


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("/hello_nodeta/testing").to(:controller => "testing", :action => "index")

Now we’re ready to deploy:

vodka:hello_nodeta% 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 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
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 (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:
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. :)