Managing HTTP cache in a RESTful web service

Reading resources over the web is always a challenging process in terms of time and network latency. As a result, when you build a web application or web service, the ability to cache and reuse previously retrieved resources is a critical aspect of optimizing for performance. The HTTP/1.1 protocol specification provides a number of features to facilitate the caching of network resources.

In this section, we will discuss the native support in the HTTP protocol for managing the cache of the resources retrieved from the server. Similarly, we will see how the JAX-RS framework APIs embrace the HTTP caching features for managing the cache of the results returned by the RESTful web services.

Using the Expires header to control the validity of the HTTP cache

You can use the Expires HTTP header field to let all entities involved in the request-response chain know when a resource is expired. The Expires HTTP header was defined as part of the HTTP/1.0 specification. You can specify the date and time in the Expires header, after which the resource fetched from the server is considered stale.

The following code snippet shows how you can add the Expires HTTP header to the resource returned by the method:

@GET
@Path("departments/{id}/holidays")
@Produces(MediaType.APPLICATION_JSON)
public Response getHolidayListForCurrentYear(@PathParam("id") Short deptId) {
    //Reads the list of holidays
    List<Date> holidayList = getHolidayListForDepartment(deptId);
    //Build response 
    Response.ResponseBuilder response = Response.ok(holidayList).
        type(MediaType.APPLICATION_JSON);
    //Set the expiry for response resource
    //This example sets validity as 
    //Dec 31 of the current year
    int currentYear = getCurrentYear();
    Calendar expirationDate = new GregorianCalendar
        (currentYear,12, 31);
    response.expires(expirationDate.getTime());
    return response.build();
}

Here is the sample response header generated by the preceding method for the GET departments/10/holidays HTTP/1.1 request:

Server: GlassFish Server Open Source Edition 4.1 
Expires: Sat, 31 Dec 2015 00:00:00 GMT 
Content-Type: application/json 
Date: Mon, 02 Mar 2015 05:24:58 GMT 
Content-Length: 20 

The Expires headers are good for the following factors:

  • Making static resources returned by the server, such as images, cacheable.
  • Controlling the caching of a resource retuned by servers that change only at specific intervals. For instance, a list of public holidays for an organization for a specific year, which does not usually change within a year.

Using Cache-Control directives to manage the HTTP cache

The Cache-Control header was defined as part of HTTP/1.1 and offers more options than the Expires HTTP header. This header is used for specifying the directives that must be obeyed by all caching mechanisms involved in the HTTP communication. These directives indicate who can do the caching of a resource returned by the server, how the caching is done, and for how long the resource can be cached.

Tip

The Expires header is recommended for static resources such as images. The Cache-Control header is useful when you need more control over how caching is done.

Here is a list of the useful Cache-Control directives:

  • private: This directive indicates only those clients who originally requested the resource (for example, browser) can do the caching and no other entity in the request-response chain (for example, proxy) is expected to do the caching.
  • public: This marks a response as cacheable and caching can be done by any entity in the request-response chain.
  • no-cache: This directive tells the client (for example, browser or proxies) that it should validate with the server before serving the resource from the cache. The validation can be done with the server by sending a request with the appropriate header fields such as If-Modified-Since, If-Unmodified-Since, If-Match, and If-None-Match.
  • no-store: This directive indicates that a response can be cached (for example, in-memory), but should not be stored on a permanent storage (for example, a disk).
  • no-transform: This directive tells that the resource should not be modified by any entity in the request-response chain. This directive is used for avoiding the loss of data while transforming a response from one format to another by intermediate entities.
  • max-age: This value (measured in seconds) indicates how long the cached resource will be considered fresh. After this, the cached content needs to be validated with the server while serving the next request.
  • s-maxage: This directive is similar to the max-age directive, except that it only applies to shared (for example, proxy) caches, not for the client who originated the request.
  • must-revalidate: This directive tells all caches that they must follow the freshness information set by the server while generating the resource. Note that the HTTP protocol allows caches to serve stale resources under special conditions. By specifying the must-revalidate directive in the header of a response, you are telling all caches not to use any stale resource from the cache and validate the expired cache resources with the server before serving the request.
  • proxy-revalidate: This directive is similar to the must-revalidate header item, except that it only applies to the proxy caches (not for the client that originally requested the resource).

Tip

A detailed discussion of the Cache-Control directives in HTTP/1.1 is available at http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html.

JAX-RS allows you to specify the Cache-Control directives on the javax.ws.rs.core.Response object returned by your REST resource method via the javax.ws.rs.core.CacheControl class. The CacheControl class exposes methods for accessing all possible Cache-Control directives.

The following code snippet illustrates the use of the CacheControl class for specifying cache expiry directives on the response object (the Department object) returned by the resource class method:

//Other imports are omitted for brevity
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder; 

@GET
@Path("departments/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response findDepartmentById(@PathParam("id") Short deptId) {

    Department department = findDepartmentEntityById(deptId);
    //Specifies max-age and private directive for the response
    CacheControl cc = new CacheControl();
    //Cache is valid for a day (86400 sec) 
    cc.setMaxAge(86400);
    cc.setPrivate(true);

    ResponseBuilder builder = Response.ok(myBook);
    //set the CacheControl object and build Response  
    builder.cacheControl(cc);
    return builder.build();
}

Here is the sample response header generated by the preceding method for the GET departments/10 HTTP/1.1 request:

Server: GlassFish Server Open Source Edition 4.1 
Cache-Control: private, no-transform, max-age=86400 
Content-Type: application/json 
Date: Mon, 02 Mar 2015 05:56:29 GMT 
Content-Length: 82

Conditional request processing with the Last-Modified HTTP response header

The Last-Modified header field value in HTTP is often used for validating the cached response contents. The Last-Modified entity-header field indicates the date and time at which the entity present in the response body was last modified. A client can use the Last-Modified header field value in combination with the If-Modified-Since or If-Unmodified-Since request headers to perform conditional requests.

The following example illustrates the use of the Last-Modified HTTP header field in the JAX-RS application. The Last-Modified field contains the date when the resource was last changed. When a client requests the same resource next time, it sends the If-Modified-Since header field, with the value set to the date and the time at which the resource was last updated on the server. On the server, you can call javax.ws.rs.core.Request::evaluatePreconditions() to check whether the resource has been modified in between the requests. This method evaluates request preconditions on the basis of the passed-in value. If this method returns null, the resource is out of date and needs to be sent back in the response. Otherwise, this method returns the 304 Not Modified HTTP status code to the client. Here is the code snippet for this example:

//Other imports are removed for brevity
import javax.ws.rs.core.Request; 
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response.ResponseBuilder;

@GET
@Path("departments/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response findDepartmentWithCacheValidationWithLastModifiedDate(
        @PathParam("id") Short id, @Context Request request) {

    //Reads the latest Department object from DB
    Department department = entityManager.find(Department.class, 
        id);
    //Gets the last modified date
    Date latModifiedDate = department.getModifiedDate();
    //Evaluates request preconditions on the basis 
    //of the passed-in value.
    //evaluatePreconditions() return null If-Modified-Since
    //check succeeds. This implies that resource is modified
    ResponseBuilder builder = 
        request.evaluatePreconditions(latModifiedDate);

    //cached resource did change; send new one
    if (builder == null) {
        builder = Response.ok(department);
        builder.lastModified(latModifiedDate);

    } 
    return builder.build();

}

Tip

To learn more about the javax.ws.rs.core.Request::evaluatePreconditions() method, read the API documentation available at https://jax-rs-spec.java.net/nonav/2.0/apidocs/javax/ws/rs/core/Request.html.

Conditional request processing with the ETag HTTP response header

Sometimes, you may find that the precision of the HTTP date object (precision in seconds) is not granular enough to decide the freshness of the cache. You can use Entity Tags (ETag) present in the HTTP response header field for such scenarios. ETag is part of HTTP/1.1 and provides a way of incorporating caching into HTTP. This tag can be used for comparing the validity of a cached object on the client side with the original object on the server.

When a server returns a response to a client, it attaches an opaque identifier in the ETag HTTP header present in the response. This identifier represents the state of the resource entity returned in response to the client's request. If the resource identified by the URI changes over time, a new identifier is assigned to ETag. When the client makes a request for a resource, it attaches the last received ETag header for the same resource (if any) as the value for the If-None-Match or If-Match HTTP header field in the request. The server uses this identifier for validating the cached representation of the resource. If the state of the requested resource has not been changed, the server responds with a 304 Not Modified status code, which instructs the client to use a copy of the resource from its local cache.

At the bottom level, ETag is different from the Client-Cache and Expires HTTP headers discussed in the previous sections. The ETag header does not have any information that the client can use to determine whether or not to make a request for a specific resource again in the future. However, when the server reads the ETag header from the client request, it can determine whether to send the resource (HTTP status code: 200 OK) or tell the client to just use the local copy (STTP status code: 304 Not Modified). You can treat ETag as a checksum for a resource that semantically changes when the content changes.

Tip

To learn more about ETag, visit the following page: http://tools.ietf.org/html/rfc7232#section-2.3.

The following code snippet shows the use of the JAX-RS APIs for building and validating ETag on the server. In this example, the client uses the If-None-Match header field to attach ETag to the request:

@GET
@Path("departments/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response findDepartmentWithCacheValidationWithEtag(
    @PathParam("id") Short deptId, @Context Request request) {

        //Reads latest department object from server
        Department department = findDepartmentEntity(deptId);
        //Builds ETag for department resource
        EntityTag etag = new 
            EntityTag(Integer.toString(department.hashCode()));

        //Checks whether client-cached resource has changed 
        //by checking ETag value present in request with value
        //generated on server
        //If changed, sends new resource with new ETag
        ResponseBuilder builder = request.evaluatePreconditions(etag);
        if (builder == null) {
            builder = Response.ok(department);
            builder.tag(etag);
       } 

    return builder.build();
}

Here is the sample response header generated by the preceding method for the GET departments/10 HTTP/1.1 request:

Server: GlassFish Server Open Source Edition 4.1 
ETag: "03cb35ca667706c68c0aad4cb04c7a211"
Content-Type: application/json 
Date: Mon, 02 Mar 2015 05:30:11 GMT 
Content-Length: 82

Conditional data update in RESTFul web services

In the previous two sections, we learned how to leverage HTTP caching features to optimize read operations (the HTTP GET type) in RESTful web APIs. In this section, we will discuss how to leverage these features during the entity updates in these APIs (the HTTP POST or PUT request types). Conditional updates help you to avoid performing modifications on the stale representation of resources, thereby avoiding the overwriting of changes performed by other users of the system on the same resource. You can perform conditional updates by using either of the following approaches:

  • Comparing the last modified date of the resource: When a client sends a modified resource to the server via the PUT or POST method, the request also carries the If-Modified-Since HTTP header field with the value set to the last modified date of the resource. The server can evaluate the If-Modified-Since header field to see whether it matches with the timestamp on the resource residing on the server.
  • Comparing Etag of the resource: If the modified date is not precise enough to decide the freshness of the resource, you can use ETag. In this case, the PUT or POST request from the client carries the If-None-Match header field with the value set to that of the ETag header sent by the server when the resource was last retrieved. The server can evaluate the value of the If-None-Match header field present in the HTTP request header to see whether it matches with the current ETag of the resource on the server.

If the validation check fails for the preceding two cases, the server will return an error response code of 412 (precondition failed). The client can take an appropriate action on the basis of the status code received as part of the response. If all checks go through, the update succeeds on the server. The following code snippet illustrates how you can use the last modified date or the ETag header present in the request for performing the conditional updates on a resource:

@PUT
@Path("etag/departments/{id}")
@Consumes(MediaType.APPLICATION_JSON)
public Response edit(@PathParam("id") Short deptId, 
    @Context Request request, Department entity) {

    //Reads latest Department object from DB and generates ETag
    Department detEntityInDB = finDepartmentEntity(deptId);
    //You can use a better algorithm for getting ETag in real life
    EntityTag etag = new 
        EntityTag(Integer.toString(detEntityInDB.hashCode()));

    //A client may pass either ETag or last modified date 
    //in the request.
    // evaluatePreconditions() returns null if the 
    //preconditions are met or a ResponseBuilder is set with 
    //the appropriate status if the preconditions are not met.
    Response.ResponseBuilder builder = 
        request.evaluatePreconditions(
            detEntityInDB.getModifiedDate(), etag);
    // Client is not up to date (send back 412)
    if (builder != null) {
        return builder.status(
            Response.Status.PRECONDITION_FAILED).build();
    }
    updateDepartmentEntity(entity);

    EntityTag newEtag = new 
        EntityTag(Integer.toString(entity.hashCode()));
    builder = Response.noContent();
    builder.lastModified(entity.getModifiedDate());
    builder.tag(newEtag);
    return builder.build();
}

Tip

The ETag and Last-Modified entity tags in the HTTP header can be used for reducing the network usage of the REST APIs by introducing the conditional retrieval of the resources on the server. You may also find it useful for the concurrency control for the REST APIs by introducing conditional updates on the server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.91.44