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. http://wiki.jruby.org/wiki/JRuby_Builtin_OpenSSL 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 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 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/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.
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 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 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: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:
<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("/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 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. :)