Monday, July 5, 2010

Spring 3: RESTful Web service Support

UPDATE: This has been updated to use JPA and a more real life type object with Id's and the like... Spring 3: RESTful Web service Support Take 2 with JPA

So in between some really long days at work, the usual before release panic and regression defects. I finally completed a simple RESTFul example, before getting into the details, here are a couple facts about RESTFul services.

  • REST stands for Representational State Transfer.
  • Each unique URL is a representation of some object.
  • You can get the contents of that object using an HTTP GET.
  • You may use a POST, PUT, or DELETE to modify the object.
  • It is not necessary to use XML as a data format, so for small amounts of data it can be significantly lighter on bandwidth.
  • REST as a protocol does not define any form of message envelope.
  • REST takes advantage of HTTP caches. (A cache can't to do anything a POST; but they can cache GETs and expire those entries based on PUTs and DELETEs.).
  • The Sun specification for RESTFul webservices can be found atJAX-RS
  • Spring does not directly support or implement the JAX-RS spec but instead RESTful functionality is added to feature Spring MVC.

As with my other examples all the source code, config and POM files are available in SVN

As we are exposing information via HTTP this is going to be a web project. I won't be going into details of compiling and deploying a web application, but will go through all the required parts
Starting with the web.xml:




The DispatcherServlet will receive the request it will then, based on the servlet mapping
go looking for "Spring3RESTFul-servlet.xml" which in turn defines the controller and view (which are dynamically searched for and created):


The component-scan in the application context above will be used to automatically pick up our @Controller. If you go to /app/users/Bob, the getUser method is executed and their respective name is passed through as a parameter. The Spring JaxB 2 marshaller will be used to marshall the User object in and out of XML. Any of the OXM marshallers can be used, I just simply prefer JaxB.

Note: You really should not have a horrible static map containing state within a controller (like I do below), I am only doing it now because this is an example, and I want to keep it simple and not digress into a full blown application. If I have time over the next week or so, I'll remove that map of data and just create an embedded DB or something.


You can view the results in your browser by simply going to the url. I am using weblogic, so mine looks like "http://localhost:7001/Spring3RESTFul-1/app/users/Mary" but depending on your deployment it could differ as long as you add "/app/users/Mary" to your context root you should get the following displayed. (if you are using firefox)


However, chances are you are not writing this to view some XML in a browser but rather to get or manipulate information from one system or component to another. For this I created a little client, this client uses Springs' RestTemplate which is autowired and lets me access the HTTP functions:

Quote from Spring blog:


DELETE delete(String, String...)
GET getForObject(String, Class, String...)
HEAD headForHeaders(String, String...)
OPTIONS optionsForAllow(String, String...)
POST postForLocation(String, Object, String...)
PUT put(String, Object, String...)


This example only uses getForObject, postForObject, put and delete for the moment, so just the normal CRUD operations.


Now to use the above client I have a test application context injecting the RestTemplate and a test case preforming the create, retrieve, update and delete.



Note:Because I just wanted to create 1 project for this example I have included the following, just so that you can compile and deploy and then run the test case... but ever adding this to a actual project POM file should be grounds for a good beating.



Lastly actual test case. Deploy the war file created by the POM to the web / application server of your choice before running the below:




And that is it... a Simple CRUD RESTFul Webservice example with Spring 3.

13 comments:

  1. "REST takes advantage of HTTP caches. (A cache can't to do anything a POST; but they can cache GETs and expire those entries based on PUTs and DELETEs.)."

    I don't get this point.

    So on browser 1 there is a get request. The browser cache it for futher access.

    Then on browser 2, another computer and all, a user perform a PUT or DELETE on the same resource.

    How browser 1 will be aware that the resource isn't available anymore and will clear it's cache ?

    ReplyDelete
  2. This refers the the HTTP server cache, not the browser cache.

    It will be configured differently for each webserver out there... below is a link to the apache docs on their caching...
    http://httpd.apache.org/docs/2.1/caching.html

    And here is another site with some more information.
    http://www.websiteoptimization.com/speed/tweak/cache/

    ReplyDelete
  3. Hi,
    The code is generating an error for the PUT method. Stack trace :

    *************************************************
    org.springframework.web.client.HttpClientErrorException: 406 Not Acceptable
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:69)
    at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:392)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:349)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:307)
    at org.springframework.web.client.RestTemplate.put(RestTemplate.java:256)
    at client.UserClient.updateUserDetails(UserClient.java:39)
    at client.UserClient.main(UserClient.java:60)

    *************************************************

    The error code being generated is quite an unusual one.
    I am not able to figure this out. Kindly help.

    Vishwa Trivedi

    ReplyDelete
  4. Hi Vishwa,

    Well I doubt I can help you with the information given, I assume you have changed a couple things. I just checked my example again and everything still works... :)

    But, I'll give it a shot anyways...

    I tried to break mine a little, but using bad data and objects... never got that exception... What is your controller doing on the method annotated with
    "@RequestMapping(method=RequestMethod.PUT)"?

    Is it returning the right type? null? exception?
    The reason I ask is the HTTP error code for 406 means:
    "Web server detects that the data it wants to return is not acceptable to the client, it returns a header containing the 406 error code"

    So I would guess you are returning something wasn't expecting..

    Just looking at the line numbers on the stack trace and in my code and the RestTemplate code they do not tie up.
    So what version of Spring are you using?

    ReplyDelete
  5. Hi Brian,
    Thanks for your reply. There seems to be some problem with the @ResponseBody annotation in the UsersController class wrt the PUT method(have removed it). I am a newbie in Spring and RESTful, so to be sure I will post the entire structure for your reference. I have made some trivial changes though and somehow I am not even aware of the repercussions.

    Thanks,
    Vishwa Trivedi

    ReplyDelete
  6. This comment has been removed by a blog administrator.

    ReplyDelete
  7. *************************************************

    Stack Trace for this code:

    Exception in thread "main" org.springframework.web.client.HttpServerErrorException: 500 Internal Server Error
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:71)
    at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:392)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:349)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:307)
    at org.springframework.web.client.RestTemplate.put(RestTemplate.java:256)
    at client.UserClient.updateUserDetails(UserClient.java:38)
    at client.UserClient.main(UserClient.java:55)
    *************************************************

    Development Environment :
    IDE : Eclipse SDK Europa
    Apache Tomcat 6.0
    JDK 1.6
    Spring 3.0
    Maven Plugin

    Thanks,
    Vishwa Trivedi
    *************************************************
    These are the details from my side.
    I am using the main() method to invoke the client methods, also I wasn't able to autowire so had to instantiate RestTemplate.

    Any pointers will be appreciated.

    Thanks,
    Vishwa Trivedi

    ReplyDelete
  8. Hi again,
    Ok, I am going to delete some of you code comments just to keep it neat.. :)

    By the looks of it, it is because you are using 3.0.0 and not the same version as me (3.0.2)...
    looking at the release notes for 3.0.1:
    http://jira.springframework.org/secure/ReleaseNote.jspa?projectId=10000&version=11331

    There seem to be quite a few fixes for:
    @ResponseBody and @RequestMapping.

    You cant get rid of '@ResponseBody', there just seems there were a couple issues with it in 3.0.0

    I have updated this example to be a more real life example, and it now uses JPA to actually create, read, update and delete to and from a MySQL DB... will have the code in SVN a little later.

    ReplyDelete
  9. Hi Brian,

    Could you inform me of a method to invoke the client without using main() method. That seems to be a major issue with my way of working.

    Regards,
    Vishwa

    ReplyDelete
  10. Hi, a main() method should be fine. I just prefer using Unit tests rather than main methods, and if you get all the code from SVN you can just use it that way.

    If you want to keep using a main(). You just need to use an application context like the applicationContext.xml I have in the src/test/resources with a ClassPathXmlApplicationContext ...

    ReplyDelete
  11. Thanks Brian,

    Able to run it using Spring 3.0.3.
    Do elaborate on the ClassPathXmlApplicationContext and how to render the response in an xml format.

    Thanks,
    Vishwa

    ReplyDelete
  12. i'm wrestling with issues similar to those you describe here. i'm having issues with the restTemplate.postForObject() method. Would you please take a look at my post here? i would appreciate any feedback. thanks for your time.
    tia

    http://stackoverflow.com/questions/3928163/spring-resttemplate-post-parameters-from-complex-object

    ReplyDelete

Popular Posts

Followers