Tuesday, September 2, 2008

RESTful URLs with Spring MVC and UrlRewriteFilter

While Spring 3.0 promises to support REST style URLs out of the box, it won’t ship until sometime later this year. Spring 3.0 M1 will offer this functionality for anyone brave enough to work with potentially unstable technologies and should be available soon. And while this is good news for those starting out on new projects (or those willing to undergo a significant refactoring), most won’t want to migrate their applications just for RESTful URLs. All hope is not lost though, thanks to Paul Tuckey’s UrlRewriteFilter.

With this short tutorial, I’ll demonstrate how easy it is to configure UrlRewriteFilter for use within your Spring MVC application. I should mention that this technique will work well for just about any Java-based web framework, like JSF or Struts.

For those of you that want to skip to the end, I’ve created a small, functional sample application which demonstrates the techniques described in this tutorial. It can be downloaded here.

Getting started, we need to register the filter within our application’s web.xml file.

web.xml

Code:

    <!-- UrlRewriteFilter -->
    <filter>
        <filter-name>UrlRewriteFilter</filter-name>
        <filter-class>
            org.tuckey.web.filters.urlrewrite.UrlRewriteFilter
        </filter-class>
        <init-param>
            <param-name>logLevel</param-name>
            <param-value>WARN</param-value>
        </init-param>
    </filter>

    <!-- UrlRewriteFilter Mapping -->
    <filter-mapping>
        <filter-name>UrlRewriteFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

That out of the way, we can dig into the application itself. Let’s review an example controller class as it might exist in your application today.

SprocketsController.java

Code:

@Controller
public class SprocketsController {

    private final SprocketService service;

    @Autowired
    public SprocketsController(SprocketService service) {
        this.service = service;
    }

    @RequestMapping("/sprockets/list.do")
    public String list(ModelMap model) {
        model.addAttribute("sprockets", service.list());
        return "sprocket/list";
    }

    @RequestMapping("/sprocket/display.do")
    public String display(@RequestParam("sprocketId") int sprocketId, ModelMap model) {
        model.addAttribute("sprocket", service.find(sprocketId));
        return "sprocket/display";
    }

    @RequestMapping("/sprocket/edit.do")
    public String edit(@RequestParam("sprocketId") int sprocketId, ModelMap model) {
        model.addAttribute("sprocket", service.find(sprocketId));
        return "sprocket/edit";
    }
}

Our controller contains actions for displaying a list of sprockets, drilling down into each sprocket’s details and editing an individual sprocket. Each action is mapped to a URI path via the @RequestMapping annotation and, as you can see, all three defined here handle requests mapped to the *.do extension. The beauty of the solution I’m demonstrating here is that you should not need to make any changes to your application code at all. That in mind, let’s move on to the interesting part, configuring our application to respond to a REST style URL like http://localhost:8080/sprocket/1234/edit instead of what we’ve got now: http://localhost:8080/sprocket/edit.do?sprocketId=1234. You’ll need to create a new configuration file named urlrewrite.xml and make it available on the classpath. (For those with a distaste for XML based configuration, the latest version of the filter offers a means to generate the configuration via annotations. Details on that can be found here.) We’ll start our configuration by defining a few rules to handle the incoming requests.

urlrewrite.xml

Code:

    <rule>
        <from>^/sprockets/$</from>
        <to>/sprockets/list.do</to>
    </rule>
    <rule>
        <from>^/sprocket/([a-z0-9]+)/$</from>
        <to>/sprocket/display.do?sprocketId=$1</to>
    </rule>
    <rule>
        <from>^/sprocket/([a-z0-9]+)/edit$</from>
        <to>/sprocket/edit.do?sprocketId=$1</to>
    </rule>

Let’s take a look at what each one of these rules does. The first rule states that the UrlRewriteFilter should transparently forward each request for http://localhost:8080/sprockets/ to the existing application URL of http://localhost:8080/sprockets/list.do. The second and third rules handle the display and edit requests. These 2 rules define simple regular expressions that are parsed out and appended to the destination URI as query string parameters. If you have more than 1 query string parameter, you’ll need to use & XML entity. (It should be noted that you can use wildcard matching (*) instead, however it lacks some of the flexibility offered by regular expressions.)

That takes care of the inbound URLs, but we still have a problem. Within our application, we have a number of links which point to the old URL structure. No worries, UrlRewriteFilter tackles this issue with ease. By examining the response, the filter can rewrite the existing links defined within anchor tags, i.e. <a href=”<c:url value=’/sprockets/list.do’/>”>Return to the list.</a>. Let’s take a look at the rule definitions to handle this.

urlrewrite.xml

Code:

    <outbound-rule>
        <from>^/sprockets/list.do$</from>
        <to>/sprockets/</to>
    </outbound-rule>
    <outbound-rule>
        <from>^/sprocket/display.do\?sprocketId=([a-z0-9]+)$</from>
        <to>/sprocket/$1/</to>
    </outbound-rule>
    <outbound-rule>
        <from>^/sprocket/edit.do\?sprocketId=([a-z0-9]+)$</from>
        <to>/sprocket/$1/edit</to>
    </outbound-rule>

More or less, these outbound rules are just the inverse of the inbound rule definitions. All it takes is a simple regular expression to parse out the sprocketId. One common gotcha to note here is that you need to add the \ character before the start of the query string marked by the question mark. If you don’t do this, the filter won’t be able to process your links.

Pretty easy, right? With your rules in place, fire up your application and give it a try. You can always examine the status of the filter by visiting http://127.0.0.1:8080/rewrite-status. If you’re a bit hesitant to try this out on your own app, fret not, I’ve created a simple, yet functional sample application which you can use to experiment with. The example source code can be downloaded here.

by Carl Sziebert

2 comments:

Anonymous said...

REST is, perhaps, a little more than URL rewriting?

ref: http://www.ics.uci.edu/%7Efielding/pubs/dissertation/top.htm

Daniel Farinha said...

Perhaps anonymous confused RESTful URLs with REST?

http://microformats.org/wiki/rest/urls