I’m wiring the Ape up so I can run it with JRuby in a servlet in a real Java App Server, and while Marcin Mielżyński’s first-cut RubyServlet works fine, I suspect it’s not the only approach to dispatching. So I’m doing some research and thinking, and I’ve collected it here for anyone who cares.
The Problem ·
Using your app-server config files, you tell it to dispatch requests to
RubyServlet, so in your web.xml
you’d have
something like this:
<servlet-mapping>
<servlet-name>RubyServlet</servlet-name>
<url-pattern>/ape/*</url-pattern>
</servlet-mapping>
So you have to design your URI space with some sort of a hook so you can route the right requests into Rubyland. Then RubyServlet has to figure out which Ruby class and method to call.
What Rails Did · Rails figures out what class and method to call based on parts of the URI path. Until recently there was also an egregious semicolon hack, but but as PragDave explains, they’re cleaning that up.
Marcin’s Solution ·
This isn’t actually, you know, documented anywhere, but Marcin’s Java code
is easy enough to read. It’s about like Rails, using
the last two parts of the URI to pick class
and method, defaulting to a method-name of index
.
It also has code CamelCasing to capitalize class-names and also (not sure why
this is done in Rails either) turn ape_drool to ApeDrool.
So:
so ape
calls Ape#index
,
ape/foo
calls Ape#foo
,
ape_drool
calls ApeDrool#index
, and
smelly/ape/dung
calls Ape#dung
(but looks for
the code in smelly/Ape.rb
).
In all cases, the Ruby code gets the Request and
Response objects, so it can find out anything it needs to about
what’s going on.
Mongrel · Mongrel’s job is easier; because in that case your Ruby code controls its server, so you can pro-actively register your URIs; docs here. For example:
h = Mongrel::HttpServer.new("0.0.0.0", "3000")
h.register("/test", SimpleHandler.new)
h.register("/files", Mongrel::DirHandler.new("."))
h.run.join
What Servlet Actually Does ·
Per the
Servlet
Javadocs, and in particular the abstract
javax.servlet.http.HttpServlet
which is what you end up using, the idea is that you fill in
methods for doGet
, doPost
and so on, and they get
called with the request and response objects.
Requests actually come in through a service
method, which they
expect you to not override, and it does the dispatching.
How Jython Works ·
Jython is clever, and took me a couple of minutes to figure out.
First, it overrides the service
method so that it can dig the
path out of the request
and do tricks similar to Rails or RubyServlet to figure out which Jython class
to call; that class has to extend HttpServlet.
Then, it compiles that Jython into bytecode, and then it
calls the generated code’s service
method, which presumbly ends
up routing to the provided doGet
and so on.
That compiling-to-bytecode trick sure is handy. JRuby guys?
The RESTless Ape · I’m perfectly OK with Marcin’s current technique; the Ape only ever calls one method, and cooking it into the URI causes me no pain. I guess I should ensure that it’s a POST not a GET because while the Ape tries to clean up after itself, it can change the state of the system.
But I kind of think the Ape is in a minority. Applications, in particular thoughtfully-designed RESTful applications, do care what the HTTP method is, and probably would like a bit more flexibility in routing requests to code than you get by mapping the last two steps in the URI path to class and method.
In fact, I suspect that the basic Java HttpServlet interface is more or less what a RESTful app designer would want, with dispatching to GET/POST/PUT/DELETE handlers. In particular, if I were building an APP server implementation, that’d be about right.
So I’ll sketch out some thoughts on what a RESTfulRubyServlet API might look like. Now, I don’t need it. In fact, maybe this is a YAGNI. But if dozens of people pipe up saying “I need that” then maybe I’ll build it; with Marcin’s code in front of me it would be no strain at all.
RESTfulRubyServlet ·
The initial problem remains; how do you figure out which class’
doGet
and so on to call?
I’d like to borrow Rails culture: not repeat myself and rely on
Convention over Configuration to make it real easy to explain what to do to get
things working.
I note that the typical structure of a Ruby project is a root directory
with src
and test
subdirectories. So, why
not use that? If you have a webapp named “foo” and
foo/WEB-INF/web.xml
says to use RESTfulRubyServlet
,
then you look for foo/src/servlet.rb
and call
Servlet#doGet
, Servlet#doPost
, and so on.
It’d be easy to build. Does the world need RESTfulRubyServlet and if so, is there a better design?
Comment feed for ongoing:
From: Dave Pawson (Apr 01 2007, at 01:32)
Tim, have a look at what Restlets have done re dispatching
http://www.restlet.org/documentation/1.0/api/org/restlet/Router.html
and
http://www.restlet.org/documentation/1.0/examples/self#part06
Seems to work nicely.
HTH
Dave P
[link]
From: Stefan Tilkov (Apr 02 2007, at 06:58)
Rails's routing DSL seems very well though-out, and could probably (with some refactoring) be used outside of Rails as well.
Here's a link for the basics:
http://manuals.rubyonrails.com/read/chapter/65
And here's how the REST support works (or used to work, with the neglectible semicolon change):
http://ryandaigle.com/articles/2006/8/1/whats-new-in-edge-rails-simply-restful-support-and-how-to-use-it
[link]
From: Colin Prince (Apr 02 2007, at 08:42)
Hi Tim, just a comment on the layout of your comments! Many times your commenters like to include useful URLs but these really stick out the sides of your layout. Perhaps for visual purposes they can be broken arbitrarily? The links themselves obviously should, still work.
Cheers and keep up the good work,
Colin.
[link]
From: James Manger (Apr 03 2007, at 05:24)
foo/WEB-INF/src/servlet.rb would be a better location, otherwise the source code is likely to be exposed directly by typing http://.
../foo/src/servlet.rb into the browser.
[link]