HTTP Conditional Updates with JAX-RS

HTTP Conditional Updates with JAX-RS

Finally I got the time to write this blog and conclude my previous blog on Concurrency using HTTP Conditional Updates. Blame it on my trip to Belur, Halebidu.

Yes, the title pic is from my trip :-)

This time apart from providing code samples, I have uploaded a complete working project on GitHub. If you want to try it out, the detailed instructions can be found in the README.md.

Recap

In the earlier blog, we discussed how HTTP provides ability to check if certain preconditions like ETag, Last updated timestamp hold for a given resource before preforming update (HTTP PUT).

This helps in avoiding race conditions that can arise due to multiple clients trying to update the same resource.

Let us see how we can implement this using JAX-RS.

Conditional PUT using JAX-RS

Our Customer bean class contains following fields:

public class Customer {
    private long id;
    private String firstName;
    private String lastName;
    private String address; 
    @XmlTransient  
    private Date createTime;
    @XmlTransient
    private Date updateTime;
}

Please note, this is not the complete class, checkout the source code at GitHub for full list. Also, createTimeupdateTime are not part of our JSON response.

We have our REST APIs in CustomerResource class. Let us first see how getCustomer() API looks:

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getCustomer(@PathParam("id") long id) {
        Customer c = customerService.getCustomer(id);
        if (c != null) {
            return Response.ok(c).lastModified(c.getUpdateTime()).build();
        }
        else {
            return Response.status(Status.NOT_FOUND).build();
        }
    }

The only difference with a conventional GET resource API is that we are also returning the update time for the Customer resource in our response.

In case you want to see how to set the ETag, here is another example:

return Response.ok(c).tag(new EntityTag("" + c.hashCode())).build();

I have shown an example using hash code as the ETag but in real implementation you would want to generate an entity tag based on your application requirements.

For this blog, I will use last updated timestamp for handling concurrent updates.

Let us see how a response to GET /customers/1 request would look like:

Let us assume two clients A, B and both performed GET /customers/1 at this point.

Next, both clients try to preform PUT /customers/1 with some change in the attributes. As we saw in the last blog that this can cause second update to overwrite changes made during the first update. This happens because the second client had an outdated representation of the Customer resource.

This is the perfect time to introduce our PUT handler and how it can take care of concurrent updates:

    @PUT
    @Path("{id}")
    @Consumes(MediaType.APPLICATION_JSON)    
    @Produces(MediaType.APPLICATION_JSON)
    public Response updateCustomer(@PathParam("id") long id, Customer customer, @Context Request request) {
        Customer c = customerService.getCustomer(id);
        if (c != null) {
            
            ResponseBuilder responseBuilder = request.evaluatePreconditions(c.getUpdateTime());
            if (responseBuilder == null) {
                customer.setId(id);
                Customer updatedCustomer = customerService.updateCustomer(customer);
                return Response.ok(updatedCustomer).lastModified(updatedCustomer.getUpdateTime()).build();
            }
            else {
                return responseBuilder.build();
            }
        }
        else {
            return Response.status(Status.NOT_FOUND).build();
        }
    }

The key here is the evaluatePreconditions() method. We have passed the current update timestamp for this resource to this method (again we can also provide an EntityTag object). The evaluatePreconditions() method will compare this value with the one passed by Client and decide whether we are good to update (HTTP 200) or not (HTTP 412).

Both Clients, in their PUT request, need to pass a new HTTP header If-Unmodified-Since with the Last Modified timestamp that we got in the response to GET request.

For the client A, this is how the response to PUT /customers/1 request would be:

As seen, the client is able to update the resource and now the Last-Modified timestamp is updated as highlighted.

At this point if client B tries to update the resource with the old representation of the resource, it will fail as shown below:

The response also indicates the current value of Last-Modified timestamp. The correct action for client B would be to perform a GET /customers/1 and then perform PUT operation.

If you see a correlation between HTTP Conditional Updates and Atomic classes’ compareAndSet() method then you are right. Both provide a mechanism to achieve concurrency using Optimistic Locking.

要查看或添加评论,请登录

Neeraj Malhotra的更多文章

  • Unit testing Spark Spring Boot Applications

    Unit testing Spark Spring Boot Applications

    Unit testing is a critical part of any production-grade application and Big Data applications are no exception…

    1 条评论
  • Integrating Spark with Spring Boot

    Integrating Spark with Spring Boot

    Source: https://relishcode.com Off late, I have started relying more and more on Spring Boot for increased productivity.

    28 条评论
  • Rising Cost of Technical Debt

    Rising Cost of Technical Debt

    Technical debt is the fallout of preferring an inferior short term solution instead of a superior long term solution…

  • Introduction to Threat Modeling

    Introduction to Threat Modeling

    Threat modeling is one of the key activity in the design phase of Security Development Lifecycle (SDL) that is promoted…

  • XSS Prevention using Input Validation

    XSS Prevention using Input Validation

    This is my last blog in the XSS Prevention series that I started after my deep dive into this area few months back…

  • XSS Prevention using HTML Sanitization

    XSS Prevention using HTML Sanitization

    In the last blog, we discussed about preventing XSS attacks by encoding the user supplied input before displaying it…

    2 条评论
  • XSS Prevention using Output Encoding

    XSS Prevention using Output Encoding

    As mentioned in the earlier blog, output encoding is the best defense against XSS. Output encoding depends on the…

  • XSS Prevention

    XSS Prevention

    Recently, I got an excellent opportunity to take deep dive into XSS (Cross Site Scripting) vulnerability. As part of my…

  • Spring REST for HTTP Conditional Updates

    Spring REST for HTTP Conditional Updates

    Since many of us use Spring Framework and have code written with Spring Web MVC, it is interesting to see how it can…

  • Concurrency using HTTP Conditional Updates

    Concurrency using HTTP Conditional Updates

    Let us assume we have multiple clients that are concurrently getting and updating shared resource like Customer…

社区洞察

其他会员也浏览了