Saturday, November 12, 2011

Restful urls and spring mvc

Spring 3.0 boasts of Rest support. One aspect of this is using resource-based urls. For example, using /hotels/hilton/suiteNo1 instead of /hotels?hotel=hilton&room=suiteNo1.

So how do we achieve this? The typical fashion of using spring mvc is to map all the incoming requests that match some pattern (e.g. *.form) to spring's DispatcherServlet like this -


    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>


This mandates that our application urls are like /hotels/hilton.form. This is not what we want here. We'd rather drop the .form if we can.

So lets map everything to the dispatcherServlet instead of just .form requests, shall we?



    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>


This enables us to drop the .form extensions. But there's a problem lurking here. A request for a static public resource like an image also goes via the dispatcherServlet. And since there won't be a controller defined for that, it'll throw a 404. We don't really want to map css and image requests to the spring servlet.
The problem can be summarised as follows : We want to map all requests except css/image/js requests to the dispatcherServlet.


How do we achieve this? Tuckey's urlrewrite comes to the rescue. In principle, its simply a Filter that lets us rewrite urls based on certain patterns. The order of processing is important. The filter will kick in _before_ the container chooses which servlet gets to handle the request. So what we need to do in the tuckey urlrewrite filter is to 'mark' all the urls which we want spring to handle with a certain prefix (say /myapp) and leave the css/image/js urls untouched. Furthermore, we configure the spring servlet to handle only those urls with that myapp prefix in them. Here's how the tuckey configuration looks:


    <rule>
        <name>Jsp Rule</name> 
        <from>**/app/**/jsp/**</from> 
        <to last="true">$1/$2/jsp/$3</to>
    </rule>
    <rule>
        <name>Css Rule</name>
        <from>**/css/**</from>
        <to last="true">$1/css/$2</to>
    </rule>
    <rule>
        <name>Js Rule</name>
        <from>**/js/**</from>
        <to last="true">$1/js/$2</to>
    </rule>
    <rule>
        <name>Spring Rule</name>
        <from>**</from>
        <to>/app/$1</to>
    </rule>

There is some duplication here - maybe all the resources can be moved under a common directory. But I'll leave that to all you responsible developers.
The 'last=true' bit is important - and something that was missing from many other blogs. Without it, tuckey just falls through to the next rule after one rule matches. So after matching /css/style.css it'll fall through to the spring rule and change it to /app/css/style.css. Won't do. The last=true means tuckey will not process subsequent rules if there is a match on that particular rule.

And there we have it. Beautiful urls.

No comments: