Thursday, February 18, 2016

Response for GET/POST/PUT/DELETE in REST web service

Before talking about the details, one thing need to be clarified. HTTP status code is designed far before RESTful web service appears.  So when REST go popular, people just try to use the most reasonable http behaviour in everyone's understanding of REST.  Here in this article I put some summary in my own experience from a developer's view  as well as other web resources such as restapitutorial.com and RFC 7231

Also this article provide demo implementation for REST by Spring webmvc, focused not on how to build rest service itself but on what to return for Get/Post/Put/Delete request for REST service.

0. Brief for HTTP status code

  • 2xx, group for success
  • 3xx, group for redirection
  • 4xx, group for client error. Client side has make a incorrect request
  • 5xx, group for server error. Server failed to fulfile the request.

In a developer's view, rest client API should return normally if the response status code is 2xx. Rest client API should throw an Excpetion if the response status code is 4xx. For example Spring's rest client, org.springframework.web.client.RestTemplate, works in that way and rightly so. 

1. Summary of REST response

image

More explanation about the above table:

The above table assume all operation is allowed. If a operation, e.g. delete on /resource to perform a delete-all operation,  is not permitted, a 405 (Method Not Allowed) should be returned. In most case, developers don’t need to anything about this. Spring’s DispatcherServlet will do that by default if it can’t find a method for the incoming request.

Usually, response 404 (Not Found) doesn't need response body at all, but can also has a body with value set to null.  Whether adding a null body or not mainly depends on method return type. (See code below for GET and DELETE, they both may return 404, but 404 from GET has a null body, 404 from DELETE hasn't, depend on the return type of the method)

GET

204(No Content) doesn't has response body. It means success but no need to reply anything to client. (Since 204 belongs to 2xx, group of success, use it as "resource not found" for GET response is inappropriate).

In all the REST response, 204(No Content) should be treat as a no-body 200(OK).

POST

Post should only work on url like /resource, not on /resource/id. Because the id of newly created resource should be assigned by server, not by client. The request body of POST needn't has the resource id value. If it has, the value of resource id will be ignored by server.

If the resource already exists in database and can not insert due to any kind of unique constraint, 409(Conflict) should be returned.

The success post response will has a "Location" http header point to newly created resource. The response body can be empty, since the resouce location is set in http header. But sometimes  can also direct return the json for newly created resource for simplicity, client need not to send an extra GET to get the assigned ID for the resource. Spring's RestTemplate class provide methods for both senarios.  It provides methods to get the 'Location' header of Post response.

public URI postForLocation(...)

Also RestTemplate provide methods like below for response with response body.

public <T> T postForObject(...)

PUT

PUT should only work on url like /resource/id, not on /resource, since modify the whole resource seems a little bit weird. The request body of POST needn't has the resource id value. Because the resource id usually on the url path.

PUT is used to update resource, in theory can also be used to create new resource according to RFC7231. But in practical whether allow "createIfNotExist" is totally up to you. Furthurmore, in real project, resources will finally get stored in DBMS like Oracle or MySQL. Usually the resource id (primary key in DB's view) is created automatically either by a identity in your Java code by JPA provider or by a sequence in Database. Which means the resource id should not be determined by client, so I personally like to disable this 'createIfNotExist' feature for PUT, just return a 404 (Not Found) for non-exist resource is good enough.

PATCH

The request body of Patch is not resource itself, but a delta. See RFC 6902 for the Json patch format.  PATCH is also used to update reousrce. It can be more effecient and atomic than PUT, since PUT need the whole resource in the request body, PATCH request body only has the difference.

DELETE 

If the delete target doesn't exist, return 404(Not Found). In some special case, the server take the delete request, but will delete the resource in async way, which means return client a 202(Accept) before the resource really get deleted.

2. Examples for REST return

Here are examples implemented with Spring webmvc for all REST method. Spring version is 4.2.1.RELEASE. With ResponseEntity's help, it's very easy to set response header, body and status code. Again examples are only focused on what to return. Suppose we have a resource entity called User. We also has 2 application defined exceptions,  ResourceAlreadyExistException and ResourceNotFoundException.

GET return demo

 @RequestMapping(path="/user",method=RequestMethod.GET)
 public ResponseEntity<List<User>> findAll() {
  // read from database
   List<User> users = userService.findAll();
  return ResponseEntity.ok(users);  // return 200, with json body
 }
 
 @RequestMapping(path="/user/{userId}",method=RequestMethod.GET)
 public ResponseEntity<User> findById(@PathVariable long userId) {
  try {
   // read from database
   User user = userService.findById(userId);
   return ResponseEntity.ok(user);  // return 200, with json body
  } catch (ResourceNotFoundException e) {
   return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); //return 404, with null body
  }
 }

The above 2 methods, first one return resource collection, second one return a specified single resource. If resource not found, return 404. Here need to set body to null to make the return type consistent as ResourceEntity<User>. (Of course, you can also use @ExceptionHandler or @ControllerAdvice to provide a more consistent return for all exceptions in real project)

POST return demo

 @RequestMapping(path="/user",method=RequestMethod.POST)
 public ResponseEntity<Void> create(@RequestBody User user) throws URISyntaxException {
  try {
   // save to database
   User newUser = userService.saveUser(user);
   return ResponseEntity.created(new URI("/user/"+newUser.getUserId())).build();
  } catch (ResourceAlreadyExistException e) {
   // log excpetion first, then return Conflict (409)
   return ResponseEntity.status(HttpStatus.CONFLICT).build();
  }
 }

The package capture for above method looks like below, you can find the response has 'Location' set in the header.

image

PUT return demo

Here we disable the 'createIfNotExist' feature for PUT, only allow it to update existing resources.

 @RequestMapping(path="/user/{userId}",method=RequestMethod.PUT)
 public ResponseEntity<Void> updateExist(@RequestBody User user,@PathVariable long userId)  {
  try {
   user.setUserId(userId);
   userService.update(user);
   return ResponseEntity.noContent().build();
  } catch (ResourceNotFoundException e) {
   return ResponseEntity.notFound().build();
  }
 }

DELETE return demo

Here the code snippet doesn't allow to  delete non-exists resource. But if you think in it'ok, you can change return to 204 or 200. 

 @RequestMapping(path="/user/{userId}",method=RequestMethod.DELETE)
 public ResponseEntity<Void> deleteById(@PathVariable long userId) {
  try {
   userService.deleteById(userId);
   return ResponseEntity.noContent().build();
  } catch (ResourceNotFoundException e) {
   return ResponseEntity.notFound().build();
  }
 }

REST client

By using RestTemplate class, It' really easy to make a REST client to consume the service. Here's an example to GET resource.

 RestTemplate restTemplate = new RestTemplate();
 String getUrl = "http://localhost:8080/spring-mvc-helloworld/user/1";
 ResponseEntity<User> entity = restTemplate.getForEntity(new URI(getUrl), User.class);
 HttpStatus statusCode = entity.getStatusCode();
 User user = entity.getBody();

3. Recap

This article mainly describes how to reponse to GET/POST/PUT/DELETE request in REST web service side.   But conventions  can be changed according to your bussiness requirement. But basic principle is always use 2xx for success and 4xx for failure. Spring also provides convenient classes like ResponseEntity and RestTemplate that you can utilize.

0 comments:

Post a Comment

Powered by Blogger.

About The Author

My Photo

Has been a senior software developer, project manager for 10+ years. Dedicate himself to Alcatel-Lucent and China Telecom for delivering software solutions.

Pages

Unordered List