Running a Rails app under Tomcat using JRuby
A new project has me looking at running a Rails app under Tomcat using JRuby [1]. A quick google revels many conflicting guides, which seem to stem from different versions of JRuby and tools (e.g. GoldSpike vs. Warbler). I ended up using this guide for getting JRuby installed, Warbler for packaging of the WAR file and this guide as a basis for setting up a project to run under Tomcat.
The issues I encountered were mainly around these conflicting guides (I recommend you read the JRuby wiki), figuring out which gems to install, which gems need to be in the WAR file and how to configure the database. Don’t take this as the canonical way to do things, it worked for a simple MySQL-backed application based on our requirements below and is accurate as of 20/05/2008 (i.e. things will probably have moved on when you read this).
I built a simple single-table (MySQL) database-backed application and managed to successfully deploy it under Tomcat as a standard WAR file. This work was intended as a spike to determine whether this was possible using the current state of JRuby and associated tools. Our main focus was to determine if the application could support CSS bundling, action/page/fragment caching, correct cache HTTP headers (ETag, expires, cache-control, etc.), compression and a high YSlow score. We’ve done this work recently for Scoodi and know Rails is capable of supporting these under Mongrel.
The good news is it seems to work as advertised.
The process
- Install JRuby (download the zip & extract into /opt), be sure to add it to your
PATH.Install the following gems (the DB ones are MySQL specific):
$ jruby -S gem install -r jruby-openssl # required for correct OpenSSL support, removes annoying nag $ jruby -S gem install -r warbler # creates WAR files $ jruby -S gem install -r rails jdbc-mysql activerecord-jdbcmysql-adapter
Your installed Gems should look like:
$ jruby -S gem list *** LOCAL GEMS *** actionmailer (2.0.2) actionpack (2.0.2) activerecord (2.0.2) activerecord-jdbc-adapter (0.8) activerecord-jdbcmysql-adapter (0.8) activeresource (2.0.2) activesupport (2.0.2) jdbc-mysql (5.0.4) jruby-openssl (0.2.3) rails (2.0.2) rake (0.8.1) rspec (1.1.3) sources (0.0.1) warbler (0.9.9)
This step actually took quite some time, as there’s many different gems available that look similar, for example if you look at the list on RubyForge with an eye towards MySQL, there’s ActiveRecord-JDBC, activerecord-jdbc-adapter, activerecord-jdbcmysql-adapter and jdbc-mysql. After you install them (or ask via gem) it’s pretty clear that some are dependencies of others, but this isn’t helpful at first.
- Create a Rails app. Based on the naming of Warbler, I chose to call it whipbird.
$ jruby -S rails whipbird $ cd whipbird
-
Warble-ise the app (this doesn’t seem to do anything to the application itself, it just creates the WAR contents in tmp, other warbler commands - see below - do allow you to make configuration changes):
$ jruby -S warble
-
Install Tomcat 6.
Copy the generated WAR file to <TOMCAT_HOME>/webapps. You should get the following in your /opt/tomcat/logs/catalina.out.
May 19, 2008 2:56:30 PM org.apache.catalina.startup.HostConfig deployWAR INFO: Deploying web application archive whipbird.war May 19, 2008 2:56:32 PM org.apache.catalina.core.StandardContext addApplicationListener INFO: The listener "org.jruby.rack.rails.RailsServletContextListener" is already configured for this context. The duplicate definition has been ignored.
I haven’t looked into what the last warning means.
-
Hit http://localhost:8080/whipbird/. You will see the default Rails page.
-
Open up the project in IntelliJ (create the project, select JRuby as the SDK). If you ask it to, IntelliJ will install and configure RSpec and RSpec on Rails as plugins in the project.
-
Configure Warbler to pull in the required gems:
$ jruby -S warble config
Add the following to config/warbler.rb:
# Gems to be packaged in the webapp. Note that Rails gems are added to this # list if vendor/rails is not present, so be sure to include rails if you # overwrite the value config.gems = ["actionmailer", "actionpack", "activerecord", "activerecord-jdbc-adapter", "activerecord-jdbcmysql-adapter", "activeresource", "activesupport", "jdbc-mysql", "jruby-openssl", "rails"] config.gems["rails"] = "2.0.2"
-
Configure the databases in config/database.yml, the ActiveRecord-JDBC page has more details (there are other ways to do this using a standard JDBC URL, but this is the easiest):
development: adapter: jdbcmysql username: root password: hostname: localhost database: whipbird_development test: adapter: jdbcmysql username: root password: hostname: localhost database: whipbird_test production: adapter: jdbcmysql username: root password: hostname: localhost database: whipbird_production
-
Create the databases:
$ mysql -u root mysql> create database whipbird_development; mysql> create database whipbird_test; mysql> create database whipbird_production;
-
Point logging at the Tomcat logs, add the following to config/environment.rb:
# Make Rails logging appear in Tomcat logs. config.logger = Logger.new(STDOUT)
-
Go ahead and create some code. I used the scaffold generator, then added migrations, modified the views, etc.:
$ jruby ./script/generate scaffold Bird
-
I also hacked together theses Rake tasks to simplify deployment of the application into Tomcat, they should really be using Capistrano.
TOMCAT_HOME="/opt/tomcat" WEBAPPS_HOME="#{TOMCAT_HOME}/webapps" APPLICATION_NAME = RAILS_ROOT.split(File::SEPARATOR).last unless defined?(APPLICATION_NAME) desc "Deploys the application, stopping and starting Tomcat" task "deploy" => ["deploy:stop", "deploy:app", "deploy:start"] namespace "deploy" do desc "Packages and deploys a WAR file to Tomcat" task "app" do puts "Creating WAR file" puts `jruby -S warble war:clean war` puts "Deploying #{APPLICATION_NAME} to #{WEBAPPS_HOME}" puts `rm -rf #{WEBAPPS_HOME}/ROOT*` puts `mv #{APPLICATION_NAME}.war #{WEBAPPS_HOME}/ROOT.war` end desc "Stop Tomcat" task "stop" do puts "Stopping Tomcat" puts `#{TOMCAT_HOME}/bin/shutdown.sh` end desc "Start Tomcat" task "start" do puts "Starting Tomcat" puts `#{TOMCAT_HOME}/bin/startup.sh` end endNote that we’re deploying the application as the Root webapp, see below for details.
-
Deploy the application:
$ jruby -S rake deploy
-
Hit http://localhost:8080/. Create some data.
-
Some other things I did to get prototype working:
- Made whipbird the default webapp by calling it ROOT.war (there are better ways to do this) and removing the old root webapp.
- Turned on caching of CSS & JS, in application.erb.html, in the standard Rails way.
- Turned on GZIP compression in Tomcat.
- In config/environments/production.rb set
config.action_controller.page_cache_directory = "#{RAILS_ROOT}/cache/" config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache/"Added caches_page :index to the top of app/controllers/birds_controller.rb.
- CSS Bundling - This works, but due to rules inside asset_tag_helper.rb, the rails app needs to run as the root webapp in Tomcat. There may be a way to fix this via config, or, you can always patch the rails source. This is not a Tomcat/JRuby problem, the same thing crops up when running mongrel with a prefix.
- JavaScript bundling - Works, with the same caveats as CSS bundling (needs to be the root webapp).
- Page Caching - Works fine. RAILS_ROOT is set to /opt/tomcat/webapps/ROOT/WEB-INF/ by default, so the default cache lives in /opt/tomcat/webapps/ROOT/WEB-INF/public/.
- Cache headers - All pages/assets contain ETags and can be compressed. They do not have expiry headers, this is usually set up in the front-end web server (e.g. Apache).
- Cache IDs - As with Mongrel-Rails, cache IDs are based on modification times of files so change when the file changes (usually on re-deployment).
- YSlow score - A (94)
Findings
My findings (based on our initial requirements) are as follows:
Stay tuned for performance stats…
[1] Rails for (initial) speed of development [2] and feature set. Tomcat as it’s our client’s current deployment container.
[2] Based on my experience with Rails to date, I don’t believe the Rails story is really as rosy as usually claimed, even for a moderately complex application, i.e. the sweet spot for a Rails app is limited.
Hi Tom,
My experience was a lot simpler, perhaps because I didn’t have the ‘install jruby’ requirement.
So all I did was install warbler and ran it in a rails app (created by calling ‘rails’ instead of jruby -S rails) and deployed the war file to tomcat. My spike was using AR with ruby mysql binding but it can be changed to use jdbc just like yours (need to add jdbc jar to WEB-INF/lib or somewhere else as long as it’s in classpath).
Cheers,
Andy
Andy Shen
22 May 08 at 3:52 am