Clone TinyURL with 40 lines of Ruby code on Google AppEngine for Java

Amidst the wretched events that happened at work recently, I forgot about an interesting development in running apps on a cloud. Google AppEngine finally released Java support on the AppEngine platform. For those uninitiated, AppEngine is Google’s cloud computing platform that allows developers to serve up applications on Google’s infrastructure. When it was first released in April 2008, the only language supported was Python. Python is a great language but doesn’t appeal to my inner Rubyist so it didn’t catch my attention. Until now that is.

While Java is no longer my language of choice nowadays, Ruby actually runs pretty well under JRuby with Java. And with the addition of the Java support for AppEngine, it became a lot more interesting. A few weeks back I wrote Snip, a TinyURL clone, in about 40 lines of Ruby code, and deployed it on Heroku. It seems like a good idea to take Snip out for a spin on the Google AppEngine for Java (GAE/J).

The first thing you need to do is to create an application on the GAE/J. Start by going to this URL – and log in using a Google account. After logging in, create a new application following the instructions given on the screen. When you’re done you should have an application id. In this case, it is ‘saush-snip’. We will be needing this application id in our configuration later. You will also need to enable Java for your GAE/J. At this point in time, GAE/J is still in beta and Google is only limiting the first 10,000 developers from enabling Java for GAE/J. Unfortunately if you don’t get Java enabled for your account, you won’t be able to try this out until it is fully released and available to all.

Once you have signed up and gotten an email approval for your GAE/J account, the first thing to do is to install JRuby, if you haven’t done so yet. Even if you have installed it previously you might want to install at least 1.3.0RC1 since some fixes were added into this release to make it run better under GAE/J. Run this:

$ git clone git://

This will clone a copy of JRuby into your computer. Then go into the jruby folder that was just created and run this:

$ sudo ant && sudo ant jar-complete

This will install JRuby and create the jruby-complete.jar library that you will need in a while. Take note of this path to JRuby, you’ll need it in the subsequent commands. Assume that you just installed JRuby in ~/jruby, do a quick check to see if the install is ok:

$ ~/jruby/bin/jruby -v

If you installed version 1.3.0RC1 you should see something like this:

jruby 1.3.0RC1 (ruby 1.8.6p287) (2009-05-11 6586) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_07) [x86_64-java]

After installing JRuby, you’ll need to install all gems needed for this application. Remember that even if you have installed gems for your normal Ruby installation you’ll need to install it all over again for JRuby. For Snip, you need Sinatra and HAML, but you’ll also need Rake and Warbler, the JRuby war file packager.

$ ~/jruby/bin/jruby -S gem install rake sinatra haml warbler

Now that the basic JRuby and related gems are done, let’s look at the Snip code itself. One thing that is pretty obvious upfront when dealing with AppEngine is that it doesn’t have a relational database for persistence. Instead of a familiar RDBMS, we get a JDO interface or a DataStore API. How do we use it? As it turns out, we don’t need to do anything major. Ola Bini wrote a small wrapper around DataStore, called Bumble, to allow us to write data models just like we did with DataMapper. Well, almost.

Using Bumble is very much similar to DataMapper and ActiveRecord, so I didn’t have to change my code much. This is the DataMapper version of the Url model:

DataMapper.setup(:default, ENV['DATABASE_URL'] || 'mysql://root:root@localhost/snip')
class Url
  include DataMapper::Resource
  property  :id,          Serial
  property  :original,    String, :length => 255
  property  :created_at,  DateTime
  def snipped() end

And this is the Bumble version of the Url model:

class Url
  include Bumble
  ds :original
  def snipped() self.key.to_s end

I didn’t add in the time stamp for the Bumble version because I don’t really use it but as you can see there are quite a bit of similarities. I didn’t need to put in my own running serial id because it’s managed by the AppEngine. Also, instead of using the object id, I used the object’s key, which again is managed by the AppEngine. A key is a unique identifier of an entity across all apps belonging to the user. The key is created automatically by Bumble through the low-level DataStore Java APIs. Besides this, using the Url class is slightly different also. Instead of

@url = Url.first(: original => uri.to_s)

We use:

@url = Url.find(: original => uri.to_s)

Finally because we don’t use the id anymore and use the key instead, we don’t need to do the base 36 conversion so we let the AppEngine handle everything. Instead of:

get '/:snipped' do redirect Url[params[:snipped].to_i(36)].original end

We use:

get '/:snipped' do redirect Url.get(params[:snipped]).original end

This is the full source code:

%w(rubygems sinatra bumble uri).each  { |lib| require lib}

get '/' do haml :index end

post '/' do
  uri = URI::parse(params[:original])
  raise "Invalid URL" unless uri.kind_of? URI::HTTP or uri.kind_of? URI::HTTPS
  @url = Url.find(:original => uri.to_s)
  @url = Url.create(:original => uri.to_s) if @url.nil?
  haml :index

get '/:snipped' do redirect Url.get(params[:snipped]).original end

error do haml :index end


class Url
  include Bumble
  ds : original
  def snipped() self.key.to_s end


@@ layout
!!! 1.1
    %title Snip! on Google AppEngine
    %link{:rel => 'stylesheet', :href => '', :type => 'text/css'}
  = yield

@@ index
%h1.title Snip! on Google AppEngine
- unless @url.nil?
  %code= @url.original
  snipped to
  %a{:href => env['HTTP_REFERER'] + @url.snipped}
    = env['HTTP_REFERER'] + @url.snipped
#err.warning= env['sinatra.error']
%form{:method => 'post', :action => '/'}
  Snip this:
  %input{:type => 'text', :name => 'original', :size => '50'}
  %input{:type => 'submit', :value => 'snip!'}
%small copyright ©
%a{:href => ''}
  Chang Sau Sheong
  %a{:href => ''}
    Full source code

The code is ready but here comes the packaging. GAE/J is a Java servlet environment, which means our app needs to be packaged into a war (Java Web ARchive). Fortunately instead of building up the war by hand we can use Warbler, the JRuby war packager. Before running Warbler, we need to have a couple of things. Firstly we need to build the warble configuration file:

$ mkdir config
$ ~/jruby/bin/jruby -S warble config

We create a directory called config and get Warbler to copy a default configuration file to it. Replace the contents with this minimal setup. If you want to explore more, read warble.rb itself. do |config|
	config.dirs = %w(lib public views)
	config.includes = FileList["appengine-web.xml", "snip.rb", "", "bumble.rb"]
	config.gems = ['sinatra', 'haml']
	config.gem_dependencies = true
	config.war_name = "saush-snip"
	config.webxml.booter = :rack
	config.webxml.jruby.init.serial = true
	config.java_libs.reject! { |lib| lib =~ /jruby-complete/ }

Note that we don’t really need the public and view directories in Snip because everything is in a single file. The 2 other configuration files we will need are appengine-web.xml and We also need to include the snip.rb and bumble.rb into the war file for deployment. To get bumble.rb, go to Ola Bini’s Bumble GitHub repository and get the file that is in the sub-folder (not the main one). The last line tells us not to include the jruby-complete.jar library in the lib folder when we run Warbler. I’ll explain this in a minute. Also note the war file is the application id of the application we created in the GAE admin console earlier on (saush-snip).

Next, create a lib folder. Go to the GAE/J download site and download the GAE/J Java library. It should be called something like appengine-api-1.0-sdk-1.2.0.jar. Copy that into the lib folder you’ve just created. We will also need the Java libraries in the lib folder. Normally for a JRuby deployment, Warbler will package it in, but Google has a 1,000 file limit which Ola Bini kindly pointed out. He also provided a script to split the JRuby library into 2 files. You can find the script here and when you run it, it should split jruby-complete.jar into 2 files named jruby-core-1.3.0RC1.jar and jruby-stdlib-1.3.0RC1.jar. You will also need JRuby-Rack but it’s included in Warbler and as you will see later, Warbler will copy it into the war when you run it. JRuby-Rack is an adapter for the Java servlet environment that allows Sinatra (or any Rack-based application) to run.

The next piece is appengine-web.xml. I used Ola Bini’s version as the base:

<appengine -web-app xmlns="">
    <static -files />
    <resource -files />
    <sessions -enabled>false</sessions>
    <system -properties>
      <property name="" value="false" />
      <property name="os.arch" value="" />
      <property name="jruby.compile.mode" value="JIT"/> <!-- JIT|FORCE|OFF -->
      <property name="jruby.compile.fastest" value="true"/>
      <property name="jruby.compile.frameless" value="true"/>
      <property name="jruby.compile.positionless" value="true"/>
      <property name="jruby.compile.threadless" value="false"/>
      <property name="jruby.compile.fastops" value="false"/>
      <property name="jruby.compile.fastcase" value="false"/>
      <property name="jruby.compile.chainsize" value="500"/>
      <property name="jruby.compile.lazyHandles" value="false"/>
      <property name="jruby.compile.peephole" value="true"/>
			<property name="jruby.rack.logging" value="stdout"/>

The list row in the property line sets logging to STDOUT, which is very useful for debugging. If you don’t set this, you might not be able to see any console output. Again, we need to set the application id that we got earlier on (saush-snip).

Finally we need a Rackup file to start the whole thing:

%w(rubygems sinatra snip).each  { |lib| require lib}
root_dir = File.dirname(__FILE__)
set :environment, :production
set :root, root_dir
set :app_file, File.join(root_dir, 'snip.rb')
disable :run
run Sinatra::Application

You can of course also find all these things in the Snip-AppEngine repository at git:// However it is so much more fun to do it step by step right?

Now that we have all the pieces let’s package our files for deployment. First we need to generate the exploded war file:

$ ~/jruby/bin/jruby -S warble

You should see output like this:

mkdir -p tmp/war/WEB-INF/gems/specifications
cp /Users/saush/jruby/lib/ruby/gems/1.8/specifications/sinatra- tmp/war/WEB-INF/gems/specifications/sinatra-
mkdir -p tmp/war/WEB-INF/gems/gems
JRuby limited openssl loaded. gem install jruby-openssl for full support.
cp /Users/saush/jruby/lib/ruby/gems/1.8/specifications/rack-0.9.1.gemspec tmp/war/WEB-INF/gems/specifications/rack-0.9.1.gemspec
cp /Users/saush/jruby/lib/ruby/gems/1.8/specifications/haml-2.0.9.gemspec tmp/war/WEB-INF/gems/specifications/haml-2.0.9.gemspec
mkdir -p tmp/war/WEB-INF/lib
mkdir -p tmp/war/WEB-INF/public
mkdir -p tmp/war/WEB-INF/views
cp lib/appengine-api-1.0-sdk-1.2.0.jar tmp/war/WEB-INF/lib/appengine-api-1.0-sdk-1.2.0.jar
cp lib/jruby-core-1.3.0RC1.jar tmp/war/WEB-INF/lib/jruby-core-1.3.0RC1.jar
cp lib/jruby-stdlib-1.3.0RC1.jar tmp/war/WEB-INF/lib/jruby-stdlib-1.3.0RC1.jar
cp appengine-web.xml tmp/war/WEB-INF/appengine-web.xml
cp snip.rb tmp/war/WEB-INF/snip.rb
cp tmp/war/WEB-INF/
cp bumble.rb tmp/war/WEB-INF/bumble.rb
cp /Users/saush/.gem/jruby/1.8/gems/warbler-0.9.13/lib/jruby-rack-0.9.4.jar tmp/war/WEB-INF/lib/jruby-rack-0.9.4.jar
cp /Users/saush/.gem/jruby/1.8/gems/warbler-0.9.13/lib/jruby-rack-0.9.4.jar tmp/war/WEB-INF/lib/jruby-rack-0.9.4.jar
mkdir -p tmp/war/WEB-INF
jar cf saush-snip.war  -C tmp/war .

You will also get a saush-snip.war file and a bunch of files under the tmp folder, which is really just the war file exploded. We won’t need the war file (saush-snip.war) itself for deployment, only the tmp directory. Before doing the deployment, we need to make a minor adjustment to the tmp/war/WEB-INF/gems/gems/sinatra- file. Somehow the line ‘use_in_file_templates!’ gives an error when deploying to the GAE/J so comment it out.

That’s it! We are ready for the deployment. To deploy run this command:

$ ~/appengine-java-sdk-1.2.0/bin/ --email= --passin update tmp/war/

You should see output like this:

Reading application configuration data...
2009-05-15 19:51:38.916::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
Beginning server interaction for saush-snip...
Password for :
0% Creating staging directory
5% Scanning for jsp files.
20% Scanning files on local disk.
25% Scanned 250 files.
28% Initiating update.
31% Cloning 340 application files.
33% Cloned 100 files.
34% Cloned 200 files.
35% Cloned 300 files.
40% Uploading 0 files.
90% Deploying new version.
95% Will check again in 1 seconds
98% Will check again in 2 seconds
99% Closing update: new version is ready to start serving.
99% Uploading index definitions.
Update complete.
Cleaning up temporary files...

Now go to and you should be able to see the new deployment of Snip on the Google AppEngine for Java.

A few other tutorials on the Internet also describe how to deploy Sinatra or Rails-based apps on GAE/J, amongst which Samuel Goebert’s tutorial and Ola Bini’s stand out the most.

A few thoughts on comparing Heroku and GAE/J since I’ve deployed on both of them now. Heroku is definitely the easier platform to deploy, with a just few simple steps compared to the hoops I had to jump for GAE/J. Heroku also has the arguably more familiar persistence mechanism as it uses the familiar RDBMS (postgresql) compared to Google’s DataStore implementation, which today only has Ola Bini’s Bumble implementation compared to the established ActiveRecord, DataMpper and Sequel ORMs. In addition, Google’s implementation has many limitations, some harder to understand than others, which forces applications to suit this platform rather than having the platform really service the application.

On the other hand, Google’s set of tools in its console is really impressive, with graphs of usage, data and log viewers. Google AppEngine also has memcached, url fetcher, integration with Google accounts, sending out mails and hosts of other helper services. Also, Google’s brand name does add some weight when you throw in more mission critical projects though Heroku, backed by Amazon’s EC2 services is no push-over.  As for costs, until large deployments emerge it is hard to say which is the better deal, also it really depends highly on usage patterns.

So are they evenly balanced out? Not for me at least. For now I would still favor Heroku over GAE/J because Heroku is more friendly to Ruby developers. But who knows? It’s exciting times for Rubyists.

39 thoughts on “Clone TinyURL with 40 lines of Ruby code on Google AppEngine for Java

  1. Май для россиян — это особенный месяц, отмеченный двумя очень важными весенними праздниками. День Победы и Праздник весны и труда — это дни, когда мы в очередной раз благодарим старшее поколение за мир и блага, ради которых они трудились и сражались.
    Спраздничком Вас

  2. Turn free movement back on and they’re going to follow you forward.
    Apple Pages templates be useful when you need to save time in creating your projects.
    If anything, the years have only increased my appreciation for Thomas & Mac – Neil’s
    swashbuckling time travel tale.

  3. U kunt ook een groot aantal andere functies in zoals spellingcontrole en tekst voorspelling.
    EEN gestroomlijnde interface met één klik toegang tot en verstrekking van video-oproepen van de desktop zelf
    ook bijdragen aan het totale video bellen. Goed, in netwerken groei van globe, een van de belangrijkste contribuanten is
    kabelinstallatie Sacramento.

  4. While you can listen to what they are saying, you need to make sure you are following your doctors directions
    and doing what is best for your body so that
    you can get better. Take control of your health and boost your immune
    system with the nutrients it needs from food and supplements chock
    full of antioxidants. He brains with a pistol two cops who pull him over.

  5. Transfer any gear require to so allows with a entire cracked,
    treatments the best gas mileage possible. ” Do keep receipts and bills of all expenses incurred in the accident to be presented to your insurer. Check hydraulic steering fluid and top off as needed.

  6. Я люблю эти цвета! Это является,
    фактически, идеальным фото.Очень приятная глубина :)
    Вот это красивое фото с очень хорошим вкусом :) Фантастические
    фото очень радуют душу.
    Ваши фотографии выглядят

  7. I’m really enjoying the design and layout of your blog.
    It’s a very easy on the eyes which makes it much more enjoyable for me to come
    here and visit more often. Did you hire out a designer to
    create your theme? Excellent work!

  8. He said he and Hayes had location riad marrakech 40 people
    no problem replying in the affirmative, and of any change in the face of college sports.
    A European Commission spokesperson refused to comment on both the Mediterranean Sea.

    Walking the old fortifications, complete with 17th century cannons,
    is like talking a walk in the woods – my daughter had her birthday tea here last year
    it was superb.

  9. It causes it to be the first choice with the users
    as well as the most wanted and comfy P2P torrent engine available in the world.
    The reasons behind installation without CD
    can be anything with the two: you do not have the Windows CD or perhaps your CD drive is just not functioning.

    So, the website walks a very thin tightrope, legally speaking.

  10. Very nice post. I simply stumbled upoon your blog and wanted to say that I have truly loved surfing around
    your weblog posts. After all I will be subscribing iin your rss feed and I am hoping
    you write once more soon!

  11. Hva er vel mer opphissende enn tanken på fri og uforpliktende
    Sex med еn fremmed. Noen av ԁe beste opplefelsene jeg har hatt har vært
    Ԁе tøffeste, meen ikle fordi det involverte vold eller smerte.
    Ɗen fullstendig oppslukte stemmen din som unnslapp en klynkende, fortvilet,
    nærmest desperat lyd.

  12. You really make it appear really easy with your presentation however I in finding this matter to be really
    something which I think I would never understand.

    It sort of feels too complicated and very vast for me. I am looking ahead in your next publish, I’ll attempt to get the dangle of it!

  13. Link exchange is nothing else but it is only placing the other person’s web site link on your page at
    appropriate place and other person will also do same in support of you.

  14. 4)Kitna must stay the most effective qb within NFC Main. About The Author Sandeep Srivastava is a renowned sports news specialist who has worked with many big widely used news portals.

    Many stage producers that are trying to give the perception of class and a luxurious setting add high-end reproductions of famous paintings to the background to spice up the scene.

  15. Spot on with this write-up, I actually believe that this
    web site needs far more attention. I’ll probably
    be returning to read more, thanks for the advice!

  16. It’s remarkable to visit this web site and reading
    the views of all colleagues concerning this piece of writing, while I am also keen of getting experience.

  17. This gene works to give instructions for making ceruloplasmin, a protein, which helps
    in the transport and processing of iron. Farxiga
    is a sodium-glucose co-transporter 2 (SGLT2) inhibitor that
    blocks the reabsorption of glucose by the kidney, increases glucose excretion, and lowers blood glucose levels.
    Certain vitamins and minerals have also been found helpful in lowering blood
    sugar and thus widely used in the treatment of diabetes.

  18. If you will get Master Resell rights, you’ll be able to sell the software program, e-book or PDF file, now
    and again video series as well for cash you decide on,
    or simply provide it with away to your subscribers.
    This article will speak about Internet marketing resulting
    in getting a master’s degree in Internet marketing online.
    Use online follow-up methods for example email and be prepared to use direct mail.

  19. I don’t know if it’s just me or if everyone else experiencing problems with your
    site. It appears like some of the written text in your posts are running off the screen. Can somebody else please provide feedback and let me know if
    this is happening to them too? This could be a problem with my web browser because I’ve had this
    happen previously. Many thanks

  20. It’s nice to sprinkle a little chili salt over your potatoes to give them that extra
    kick. Paying homage to your family’s delectable culinary
    prowess can be a thoughtful gift for a mother, grandmother or mother-in-law
    as well. There is an endless range of craft and
    make-your-own packs available using a range of

  21. Have you ever spared a thought on answering is it safe to use this mode of shopping.
    There are other advantages to online shopping that can help
    you to reduce your carbon footprint. It’s true: there are a number of people
    out there who would love nothing more than to take your money.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s