Search This Blog

Monday, 8 July 2013

POST with a location

IN the last few posts I have been trying out Springs REST API. For the create operation we had used a POST call. The API would take an user resource, create it and return the user resource in the response stream.
This approach of returning the created resource has served me fine. However traversing the net has revealed some best practices. One such practice related to Create operation requires:
  • The API need not return the created resource in the Response Stream
  • It should return an empty response body
  • Instead the API must return a HTTP Status 201 indicating the resource was created. 
  • Also in the location header of the response it must return a URL needed to access the newly created resource. The client can if it needs fetch the new resource by making a GET call to the returned URL.
This approach is best used when you are working with heavy resources. For example if the resource created is a large and complex object may be heading into several hundred KB, than the approach is useful. Especially when working with APIs that are accessed from code.
My APIs have generally been used from AJAX libraries where the best approach seemed to return the created request for use in UI. The resources were also pretty small. But if we developing for heavy resources than the above approach of using Location headers seems a good option. Accordingly I added an additional CREATE method in my User API:
@RequestMapping(value = "/new", method = RequestMethod.POST)
   public ResponseEntity<Void> addRedirectUser(@RequestBody final User newUser) {
      // create the new resource
      this.addUserToSystem(newUser);
      logger.info("addRedirectUser : new wine added with id " + newUser.getId());

      final URI location = ServletUriComponentsBuilder
            .fromCurrentServletMapping().path("/user/{id}").build()
            .expand(newUser.getId()).toUri();

      final HttpHeaders headers = new HttpHeaders();
      headers.setLocation(location);

      final ResponseEntity<Void> entity = new ResponseEntity<Void>(headers,
            HttpStatus.CREATED);
      return entity;
   }
To return the dynamic location URL I used the ResponseEntity instance. The body was set to type Void. The Headers included the new URL. The response Entity created was written back by The DispatcherServlet. (Note: No ResponseBody annotation was used here)
The code to test the same is:
public User createUserV2(final User newUser) {
      final HttpEntity<User> userRequest = new HttpEntity<User>(newUser);
      final URI createdURI = this.restTemplate.postForLocation(userServiceUrl
            + "new", userRequest);
      return this.restTemplate.getForObject(createdURI, User.class);
   }
Here we have used the postforLocation method. The method reads the URL from the location header of the response and returns it. A get call was then made to fetch the newly created entity.
On running the code the logs at server is:
2013-06-15 18:35:24 INFO  RestServiceController:102 - addRedirectUser : new wine
 added with id 3
2013-06-15 18:35:24 DEBUG ServletInvocableHandlerMethod:129 - Method [addRedirec
tUser] returned [<201 Created,{Location=[http://localhost:8080/SampleRest/api/us
er/3]}>]
...
2013-06-15 18:35:24 DEBUG DispatcherServlet:913 - Successfully completed request
2013-06-15 18:35:24 DEBUG XmlWebApplicationContext:322 - Publishing event in Web
ApplicationContext for namespace 'dispatcher-servlet': ServletRequestHandledEven
t: url=[/SampleRest/api/user/new]; client=[127.0.0.1]; method=[POST]; servlet=[d
ispatcher]; session=[null]; user=[null]; time=[703ms]; status=[OK]
...
2013-06-15 18:35:24 DEBUG DispatcherServlet:819 - DispatcherServlet with name 'd
ispatcher' processing GET request for [/SampleRest/api/user/3]
2013-06-15 18:35:25 INFO  RestServiceController:66 - getUser with id 3
...
2013-06-15 18:35:25 DEBUG DispatcherServlet:913 - Successfully completed request
2013-06-15 18:35:25 DEBUG XmlWebApplicationContext:322 - Publishing event in Web
ApplicationContext for namespace 'dispatcher-servlet': ServletRequestHandledEven
t: url=[/SampleRest/api/user/3]; client=[127.0.0.1]; method=[GET]; servlet=[disp
atcher]; session=[null]; user=[null]; time=[515ms]; status=[OK]
As seen, the client made two calls -
  1. A POST call to create the new record
  2. A GET call to return the newly created record.
The logs at the client indicate it.
2013-06-15 18:35:23 DEBUG RestTemplate:78 - Created POST request for "http://loc
alhost:8080/SampleRest/api/user/new"
2013-06-15 18:35:23 DEBUG RestTemplate:592 - Writing [com.test.controller.User@1
f2cea2] using [org.springframework.http.converter.json.MappingJacksonHttpMessage
Converter@1dc0e7a]
2013-06-15 18:35:24 DEBUG RestTemplate:473 - POST request for "http://localhost:
8080/SampleRest/api/user/new" resulted in 201 (Created)
2013-06-15 18:35:24 DEBUG RestTemplate:78 - Created GET request for "http://loca
lhost:8080/SampleRest/api/user/3"
...
User created with id 3

2 comments: