Thursday, October 2, 2008

Making your servlet application URLs more Restful

If you are a developer who likes to work with Java you may have come to know REST and Restful Web Services. The real advantage of REST is its ability to get rid of some of the "webspeak" and make your URLs a little more platform independent. As a client developer it is much nicer to make a request to http://somehost/Application/stamp/5533 to retrieve stamp 5533 than the traditional http://somehost/Application/servlet/StampServlet?id=5533. Theoretically you could rewrite your application services layer without modifying the client. If you are not familiar with REST and Restful Web Services, a good dissertation on this can be found here by Roger L. Costello.

So all of this is nice, but how can we apply this to a servlet-based Web Application? There are projects out there such as the Java Restlet API, and while I think this is good, it does mean essentially having a non-servlet compatible solution (since the Restlet takes the place of a servlet). Instead, you can take advantage of some of the REST themes by using servlet-based Web Applications following these steps:

  • We want to write a Rest-like servlet for accessing Stamps. We have a servlet of the class StampServlet which is mapped in the web.xml of our servlet container as stamp. This would look like the following:
    <servlet> <description>Servlet for processing Stamp Restful requests</description> <servlet-name>stamp</servlet-name> <servlet-class>org.javad.stamp.servlet.StampServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>stamp</servlet-name> <url-pattern>/stamp/*</url-pattern> </servlet-mapping>

    The key here is the /* in the url-pattern of the servlet-mapping. This will mean any URI that is received that starts with "stamp" will send it to the StampServlet, but we can use any of the information on the URI chain to help direct the servlet to process the request in a Restful way. For example, if we are retrieving the details of a stamp our URL would look like:
    http://hostname/StampApp/stamp/563

    Using the GET method, where the ID of the stamp in this case is 563. If this was a modify function, the URL would look similar only a method of PUT would be used.
  • REST uses many of the less-popular methods of HTTP. In particular the PUT and DELETE methods. Fortunately, the HttpServlet class does implement these with the doPut(HttpServletRequest,HttpServletResponse) and doDelete(HttpServletRequest,HttpServletResponse) methods. If you are not comfortable using these methods (for example the HttpServlet Javadoc does mention PUT for use in placing files on the server like FTP), we can make our URLs be a little less Restful, but still Restlike by using POST method and inserting a keyword after the ID of the object in question. I should note, that for some applications like GWT, using protocols other than POST or GET is difficult. In GWT we can attach a header value (X-HTTP-Method-Override) to indicate we'd like to use DELETE, but it is still sent to the POST method receiver initially. In this case, using a POST method URL along with the method override header we can still have a Restful URL that would look like the following:
    <form action="http://hostname/StampApp/stamp/563" method="POST"> </form>

    More practical would be the use in an AJAX application using the XMLHttpRequest in Javascript (since we can override the X-HTTP-Method-Override header:
    var req = new XMLHttpRequest(); req.open( "POST", "http://hostname/StampApp/stamp/563" ); req.setRequestHeader("X-HTTP-Method-Override","DELETE"); req.send();
  • URL Construction is great, but how do I make use of this in my servlet doPOST( )? Since we mapped the servlet with a /*, anything to the right of the stamp in the URI is treated as part of the PathInfo. Therefore within your doPOST() method, you can request the path-info and take the appropriate action. Similar to the following:
    protected void doPost(HttpServletRequest request, HttpServletResponse response) { String pathInfo = request.getPathInfo(); Method method = request.getHeader("X-HTTP-Method-Override"); if( pathInfo.isEmpty() ) { createNewStamp( ); // purely a POST } else if( method != null && pathInfo != null) { long id = Integer.parseInt( pathInfo.split("/")[0]); if ("DELETE".equalsIgnoreCase( method ) ) { doDelete(request,response); // or call deleteStamp(id); } else if ( "PUT".equalsIgnoreCase( method )) { doPut( request, response ); // or call modifyStamp(id); } else { // if there are further elements in the pathInfo call the appropriate code... } } else { log("POST method called for stamp details without Method Override header. Use GET method to retrieve stamp details or specify a Method Override header."); } }

While this is not perfect, it is certainly easier to program a client to a URI like stamp/563 than the traditional way of stamp?id=563&action=DELETE. In the above example, it is likely that the code for servicing an action like Delete is in a specified method, so instead of trying to call the doDelete( ) simply a call to deleteStamp(id) would probably be more appropriate. Using this technique allows you to support both methods of processing your objects in a consistent way, while being flexible in support for the client technology. While it does muck up your doPost() methods a little bit this is a minor tradeoff for more readable URIs.

I should also mention that additional values in the pathInfo are used in Rest to indicate additional actions to take place against that object. For example:

http://hostname/StampApp/stamp/563/catalogues

With a method of GET would refer to a request to retrieve the catalogues for the stamp with the ID 563. In this situation having a controller (such as that provided by the Restful application) would definitely help in forwarding these to the correct service. Depending on the complexity of your application, you may be able to simply do this internally within your servlet such as in the else case mentioned above, however for more than a few actions this can be complex. Especially if catalogues (using the example above) can exist outside of the context of a stamp. This would mean you'd have to provide a servlet or some application that can process catalogues and find a way to tie the stamp servlet with the catalogue servlet. The worst case I can think of in my application would be trying to get all of the stamps that are in a country, filtered by an album in a stamp collection. This might look like:
http://hostname/StampApp/collection/56/album/25/country/76/stamps

As you can see, this is not quite as readable. Since I can also get stamps by album or simply by collection I might be more prone to simple request the stamps for collection 56 and then take on the album/country ids as queryString data:

http://hostname/StampApp/collection/56/stamps?album=25&country=76

Not pure Rest, but Rest-like. If I truly did want to retain the Restful URI, I would likely write a controller which returned stamps, and if the pathInfo of the GET request for the collection servlet contained stamps in it I would directly call it passing the pathInfo and then allow the controller to decide how to filter the URI (in my case I have a StampFilter object which accepts the three objects and calls the appropriate JPA Query based on the filter setup so this would be quite easy for me to do).

1 comment:

Venkat Mantirraju said...

This is best post on how to make use of simple servlet to serve the restful calls without using CXF or Axis or some other ehavy weight stuff for basic rest needs.