Parameterized REST URLs with Spring MVC

At Carbon Five, we’ve been working REST-ful practices into our web applications for some time now. Providing simple URLs for application entities is a key principal of this style, but parsing parameters out of the request path has been klunky in Spring MVC. Spring’s WebFlow apparently supports REST-ful URLs, but I’ve never found anything in what I’ve read or heard to recommended that project (though I’ve heard nothing bad).

I finally got fed up with the situation and worked out a solution that lets developers specify path parameters in the dispatcher mappings, which will appear as request parameters in the controller and view. I’ve made the project available on our public SVN server. The solution requires only 4 classes, so you can download them instead. Read on for configuration information.

ParameterizedUrlHandlerMapping

The solution is to replace Spring MVC’s SimpleUrlHandlerMapping with ParameterizedUrlHandlerMapping. This class is responsible for routing requests to the appropriate handler (servlet, controller, or JSP), so it’s an ideal place to specify the parameters. Of course, at this point, it has no more access to the request than a controller, so the best it can do is add the path parameters to the request attributes. It adds them as a Map of String name/value pairs with the key “ParameterizedUrlHandlerMapping.path-parameters”.

To use ParameterizedUrlHandlerMapping, replace the SimpleUrlHandlerMapping bean in the dispatcher configuration file
with something that looks like this:

<bean class="carbonfive.spring.web.pathparameter.ParameterizedUrlHandlerMapping">
  <property name="alwaysUseFullPath" value="true"/>
  <property name="mappings">
    <props>
      <prop key="/view/noparameters">controller1</prop>
      <prop key="/view/(bar:foo)">controller2</prop>
      <prop key="/view/(*.html:html)">controller3</prop>
      <prop key="/view/(**/*:view).view">controller4</prop>
      <prop key="/view/c/(*:controller)/(*:id)">controller5</prop>
    </props>
  </property>
</bean>

The ParameterizedUrlHandlerMapping supports all mappings that are valid using SimpleUrlHandlerMapping’s default AntPathMatcher. The special parenthetical sections of the patterns have the syntax:

'(' + [ant_style_path] + ':' + [parameter_name] + ')'

Any part of the path pattern can be within parenthesis. The above example will having the following effect:

.content table {
width: 100%;
border: 1px solid #ccc;
}
.content th {
background-color:#eee;
}
.content td {
background-color: #f5f5f5;
}

path controller parameters
/view/noparameters controller1
/view/bar controller2 foo -> bar
/view/piglet.html controller3 html -> piglet.html
/view/this/that/the-other.view controller4 view -> this/that/the-other
/view/c/save/2342443 controller5 controller -> save
id -> 2342443

ParameterizedPathFilter

If accessing the parameters from request attributes is all you need, you can stop here. But to elevate your path parameters to first class citizen status in your web app, you need them to appear as request parameters. Once they are request parameters, Spring will bind them to your command or form objects, and your controllers can once again forget the details of request parsing and focus on business logic. To complete the solution, we need the ParameterizedPathFilter configured as a web filter. It proxies the request with a wrapper class that listens for ParameterizedUrlHandlerMapping’s request attribute. When it is set, the wrapper class adds the parameters to the request parameters for the remaining life of the request.

To configure the ParameterizedPathFitler, add the following to your web.xml:

<filter>
  <filter-name>PathParameterFilter</filter-name>
  <filter-class>carbonfive.spring.web.pathparameter.ParameterizedPathFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>PathParameterFilter</filter-name>
  <url-pattern>/path/to/dispatcher/servlet/*</url-pattern>
</filter-mapping>

You should have a filter mapping for each path pattern mapped to a Spring dispatcher servlet.

That’s all it takes to add this functionality to your webapp. We’ve been using it in our current project. It’s been problem free and the architectural improvements have been satisfying.

About Alex Cruikshank

Alex is Carbon Five's resident mad genius. He is recently very involved in functional languages, and has been anchoring teams at C5 for the past decade.
This entry was posted in Web and tagged . Bookmark the permalink.

20 Responses to Parameterized REST URLs with Spring MVC

  1. Kim Pepper says:

    You could also use something like UrlRewrite Filter to map all your routes, converting path parameters to query parameters using regular expressions.

    http://tuckey.org/urlrewrite/

  2. Alon says:

    I don’t believe URLRewriteFilter does provide the ability to set query parameters.

    http://tuckey.org/urlrewrite/manual/3.0/index.html#configuration

    You can set attributes in the request but the docs do not indicate that you can set parameters. Of course, it would be a possible feature using the same approach as Alex’s Filter. UrlRewriteFilter does already wrap the request and response which is where you would add this feature.

  3. Andy says:

    I’m trying to use this as a filter so to continue using my command bean setup with with the REST-style URLs.

    However, it seems that when passing multiple parameters like in the example: /view/c/save/2342443, each of them end up with a trailing slash except the last one.

    So using the example from above, a call to cmd.getController() would return “save/” instead of just “save” and cmd.getId() would return “2342443”.

    I’m wondering if you can replicate this behaviour or if something that I’m doing wrongly…but it seems pretty straight-forward…

  4. I tried to use the ParameterizedUrlHandlerMapping with a simple mapping like “/user/(*:user)/found” but strangely that sets user to “stefan/” when I request “/user/stefan/found”.

  5. Kim Pepper says:

    You could try something like:

    <rule>
      <from>^/world/([a-z]+)/([a-z]+)$</from>
      <to>/world.jsp?country=$1&city=$2</to>
    </rule>

  6. To Stefan and Alon:
    I had made an error in the dispatcher configuration in this post. The line “<prop key=”/view/c/(*:controller)/(*:id)”>controller5</prop>” used to have back slashes before the colons. It’s necessary to escape the colons if you use the <property name=”mappings”><value>prop1=value1</value></property> syntax, but not if you use the <property name=”mappings><props><prop key=”…”>…</prop></props><property> syntax. If you escape the colons in the latter syntax, parameters taken from within the path will have a slash appended. I’m not sure exactly why. I would expect that escaping the colon would either behave identically or cause it to miss the parameter altogether. Anyway, sorry about the mistake. It’s been corrected in the text.

    To Kim:
    I agree that a regexp-based rule syntax would be superior, but I want this to be a drop-in extension to spring’s URLHandlerMapping (or at least as close as possible). Its important that developers not have to configure their mappings in two different places, and I want to use the existing spring functionality for the actual routing of requests. These requirements led me to the current set of classes. Given these constraints, the only real flexibility in syntax is in the way we map a segment of the ant style path pattern to a parameter name. Thanks for the suggestion though.

  7. dave says:

    This looks like an interesting approach to implementing REST-ful urls.

    One question — I didn’t see a LICENSE.txt in the svn repository. Is this code available for use by the general public?

  8. Excellent point, Dave. I’ve just checked in an Apache 2.0 style license to the project and included it within each of the class files. Hopefully this makes adoptable by anyone that has a need for it.

  9. vin says:

    Hi …

    Is it worked correctly to anyone? I am stuck at compilation itself at ParameterizedUrlHandlerMapping class.


    Map handlerMap = (Map) getHandlerMap();

    It complaits that it cannot find the getHandelerMap() method.

    Please any insights ,,,

    Thanks
    Vin

  10. Hi Vin,
    This is almost certainly a problem with your Spring MVC dependency. Either you’re missing the Spring jars altogether, or you have an older version with a different API (one in which SimpleUrlHandlerMapping has no getHandlerMap() method). The jars are available at http://www.springframework.org . If you are going to compile the stand-alone java files, you are also going to need the servelet API in your classpath. Hope this helps – alex.

  11. Steve Mayhew says:

    Alex. Thanks! Works great. Only problem is the ParameterizedPathFilter did not work for us, since the handler is a Controller subclass so request does not go back through the RequestDispatcher. Tell Don I say hello!

  12. Steve Mayhew says:

    I used a small HandlerAdapter in place of the Filter:
    public class ParameterizedUrlHandlerAdapter extends SimpleControllerHandlerAdapter implements HandlerAdapter {

    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    if (request.getAttribute(ParameterizedUrlHandlerMapping.PATH_PARAMETERS) != null) {
    request = new ParameterizedPathServletRequest(request);
    }
    return super.handle(request, response, handler);
    }
    }

  13. Hi Steve,
    I’m glad you found a solution that works for you. I considered using a HandlerAdapter instead of a filter. It’s much cleaner, but it only wraps the controller, not the view, so the additional parameters won’t be available to the JSP (or whatever technology you’re using). You should note that the request does not need to go back through the filter after processing by the Dispatcher servlet. So long as it hit the filter before the dispatcher servlet, the request wrapper will add the parameters as soon as the magic attribute is set. Thanks for your contribution.

  14. Anthony says:

    Logan is down it seems. I am trying to get the source to look at it. Could you check what’s going on?
    Would you be so kind to email me the source otherwise?
    Thank you much!

  15. Harry says:

    This sounds like a great utility, but there seems to be something wrong with the svn server. Specifically, when I click on the download link in the article, it says:

    “logan.carbonfive.com has sent an incorrect or unexpected message. Error Code: -12263″

    Can I get the code somewhere else?

  16. Alon says:

    Ah, yes. SVN now has a dedicated server so the URL has changed. I fixed the links in the post above.

  17. charfles says:

    Thanks for this. It has made my job incredibly easier.

    I just wanted to say that if you want this to work with annotated controllers and @RequestMapping annotations then you should change the ParameterizedUrlHandlerMapping class to extend DefaultAnnotationHandlerMapping from the spring library instead. This simple change will allow you to use requestmappings and requestparam annotations.

    Thanks again for this.

  18. john says:

    I’d love to figure out how to use this. Maybe I am missing something..

    I want to use urls like:

    http://mydomain.com/mywebapp/1/2/3/4/5/6/7/8/10
    http://mydomain.com/mywebapp/search/somename/someaddress

    and so on.

    Seems I can’t figure out how to allow a single controller handle all cases where it can just get ALL the values aftert the initial /mywebapp context.

    I am still learning Spring, very confused with the variety of bean wiring required. Honestly Struts seemed easier to just specify the action class, bean(s), and go. But Spring has so much industry interest I want to learn it.

    So can you point out how I might set a controller in the mywebapp-servlet.xml file so that I can just get the list of values after the webapp context name, and do with them as needed?

    Thank you.

  19. Rachel says:

    We had a problem while using the c:param tag of our jsp’s. The parameters were not getting set onto the request correctly. So we changed the getParameterValues() method in the ParameterizedPathFiler to get it to set these parameters. This is our new method.

    @Override
    public String[] getParameterValues(String string) {
    String[] values = parameters.get(string);
    if(values != null && values.length > 0) {
    return values;
    }

    return super.getParameterValues(string);
    }

    Other than this, the code works wonderfully! Thanks :)

  20. Timo Westkämper says:

    We had some issues with more complex matchers. The following test case describes a failing case :

    @Test
    public void testSpecialCases(){
    assertTrue( pathMatcher.match( “users/(*:user)”, “users/admin” ) );
    assertFalse( pathMatcher.match( “users/(*:user)/(*:tag)”, “users/admin” ) );
    }

    It seems that the second pattern “users/(*:user)/(*:tag)” is internally transformed into

    users[\/]([^\/]*)[\/]?([^\/]*)

    but it should

    users[\/]([^\/]*)[\/]([^\/]*)

    -> the second path separator is mandatory, not optional

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>