ETags

Our Twitter results are neatly cached, so a user refreshing the result page will not trigger an additional search on the Twitter API. However, the response will be sent to this user multiple times even if the results do not change, which will waste bandwidth.

An ETag is a hash of the data of a web response and is sent as a header. The client can memorize the ETag of a resource and send the last known version to the server with the If-None-Match header. This allows the server to answer 304 Not Modified if the request does not change in the meantime.

Spring has a special Servlet filter, called ShallowEtagHeaderFilter, to handle ETags. Simply add it as a bean in the MasterSpringMvc4Application configuration class:

@Bean
public Filter etagFilter() {
    return new ShallowEtagHeaderFilter();
}

This will automatically generate ETags for your responses as long as the response has no cache control headers.

Now if we interrogate our RESTful API, we can see that an ETag is sent along with the server response:

> http GET 'http://localhost:8080/api/search/mixed;keywords=spring' -a admin:admin
HTTP/1.1 200 OK
Content-Length: 1276
Content-Type: application/json;charset=UTF-8
Date: Mon, 01 Jun 2015 11:29:51 GMT
ETag: "00a66d6dd835b6c7c60638eab976c4dd7"
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=662848E4F927EE9A1BA2006686ECFE4C; Path=/; HttpOnly

Now if we request the same resource one more time, specifying the last ETag that we know of in the If-None-Match headers, the server will automatically respond with a 304 Not Modified status:

> http GET 'http://localhost:8080/api/search/mixed;keywords=spring' If-None-Match:'"00a66d6dd835b6c7c60638eab976c4dd7"' -a admin:admin
HTTP/1.1 304 Not Modified
Date: Mon, 01 Jun 2015 11:34:21 GMT
ETag: "00a66d6dd835b6c7c60638eab976c4dd7"
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=CA956010CF268056C241B0674C6C5AB2; Path=/; HttpOnly

Tip

Due to the parallel nature of our search, the tweets fetched for different keywords might arrive in different orders, which will make the ETag change. If you want this technique to work for multiple searches, please consider ordering your search results before sending them to the client.

If we want to take advantage of that, we obviously need to rewrite our client code to handle them. We will see a simple solution to do that with jQuery, using the local storage of the browser to save the latest query of the user.

First, remove the tweets variable from our model; we won't do the search from the server anymore. You will have to modify a test or two to reflect this change.

Before going further, let's add lodash to our JavaScript libraries. If you don't know lodash, let's say it is the Apache Utils of JavaScript. You can add it to your project dependencies like so:

compile 'org.webjars.bower:lodash:3.9.3'

Add it to the default.html layout, just under the materialize's JavaScript:

<script src="/webjars/lodash/3.9.3/lodash.js"></script>

We will modify the resultPage.html file and leave the part where the tweets should appear empty:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layout/default">
<head lang="en">
    <title>Hello twitter</title>
</head>
<body>
<div class="row" layout:fragment="content">

    <h2 class="indigo-text center" th:text="|Tweet results for ${search}|">Tweets</h2>

    <ul id="tweets" class="collection">
    </ul>
</div>
</body>
</html>

Then, we will add a script element at the bottom of the page, just before closing the body:

<script layout:fragment="script" th:inline="javascript">
    /*<![CDATA[*/
    var baseUrl = /*[[@{/api/search}]]*/ "/";
    var currentLocation = window.location.href;
    var search = currentLocation.substr(currentLocation.lastIndexOf('/'));
    var url = baseUrl + search;
    /*]]>*/
</script>

The preceding script will just be in charge of constructing the URL for our request. We will use it by issuing a simple jQuery AJAX call:

$.ajax({
    url: url,
    type: "GET",
    beforeSend: setEtag,
    success: onResponse
});

We will use the beforeSend callback to have a chance to modify the request headers just before the call is made:

function getLastQuery() {
    return JSON.parse(localStorage.getItem('lastQuery')) || {};
}

function storeQuery(query) {
    localStorage.setItem('lastQuery', JSON.stringify(query));
}

function setEtag(xhr) {
    xhr.setRequestHeader('If-None-Match', getLastQuery().etag)
}

As you can see, we can easily read and write from local storage. The gotcha here is that local storage only works with strings so we have to parse and serialize the query object to JSON.

We can handle the response by retrieving the content from local storage if the HTTP status is 304 Not Modified:

function onResponse(tweets, status, xhr) {
  if (xhr.status == 304) {
      console.log('Response has not changed');
      tweets = getLastQuery().tweets
  }

  var etag = xhr.getResponseHeader('Etag');
  storeQuery({tweets: tweets, etag: etag});

  displayTweets(tweets);
}

function displayTweets(tweets) {
  $('#tweets').empty();
  $.each(tweets, function (index, tweet) {
      addTweet(tweet);
  })
}

For the addTweet function that you will see next, I'm using lodash, a very useful JavaScript utility library, to generate templates. The function to add tweets to the page can be written as follows:

function addTweet(tweet) {
    var template = _.template('<li class="collection-item avatar">' +
        '<img class="circle" src="${tweet.profileImageUrl}" />' +
        '<span class="title">${tweet.user}</span>' +
        '<p>${tweet.text}</p>' +
        '</li>');

    $('#tweets').append(template({tweet: tweet}));
}

That was a lot of JavaScript! It would make more sense to generalize this pattern in a Single Page Application using a library such as Backbone.js. Hopefully, though, this will serve as a simple example of how to implement ETags in your application.

If you try to refresh the search page multiple times, you will see that the contents do not change and will be displayed immediately:

ETags

There are other uses for ETags, such as optimistic locking for transactions (it lets you know on which version of an object the client is supposed to be working on at any time). It is also extra work on the server side to hash the data before sending it across, but it will save bandwidth.

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

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