Tutorial on developing rest Web services with SSH to support HTTP etag headers

Source: Internet
Author: User
Tags event listener http etag md5 hash

Original finishing is not easy, reproduced please indicate the source: using SSH to develop rest Web Services support HTTP ETag Header tutorial detailed

Code: http://www.zuidaima.com/share/1777391667989504.htm


Introduction

The great impact of the rest-mode application architecture in recent days has highlighted the importance of elegant design for Web applications. Now people are beginning to understand the intrinsic scalability and resilience of the "WWW architecture" and have begun to explore better ways to use its paradigm. In this article, we will discuss a Web application development tool-"humble, humble" etags, and how to integrate this tool in a springframework-based dynamic Web application to improve the performance and testability of the application.

The spring-based application we will be using is based on "Petclinic" (Pet Clinic?). ) is an application. In the package that you downloaded, it contains a description of how to add the necessary configuration and source code to let you experience the program yourself.

What is an etag?

In the HTTP protocol specification, the etag is defined as "the entity value of the variable being requested." (
See Http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html-Section 14.19. In other words, the etag is a tag that is associated with a Web resource. A typical web resource is a Web page, but it can also be a JSON-formatted or XML-formatted document. The server can indicate what a token is and what its meaning is, and place the token on the HTTP header to be transmitted to the client.

How ETag improves application performance

The etag is used with the "If-none-match" header information for a GET request, which the server developer uses to benefit from client-side caching. The server generates the ETag at a request from the client and, in a subsequent request, determines whether the requested resource has changed. Specifically, the client passes this token back to the server to verify that its own cache is valid.

The entire process is as follows:

Client Request Page A
Server response, back to page A, additional ETag
The client displays a and caches the page and the ETag
The client requests page A again, and the request contains the etag that was returned when page A was last requested
The server checks the etag sent over by the client and determines that page A has not changed since the last request of the client, and therefore sends a 304 (unchanged) response header to the client, with an empty response body.

The remainder of the article will discuss the use of ETag in the Springframework-based Web application using SPRINGMVC two ways. First, we will use an Servlet2.3 filter that produces an etag (a simple etag implementation) that results from the MD5 value returned by the calculation request. The second approach uses a more "professional" approach to determine the effectiveness of the ETag (a "professional" ETag implementation) by tracking the changes in the model used to render the page. Although we are using spring MVC here, this technique applies to any other MVC framework.

Before proceeding, it is necessary to make clear that ETag technology is intended to improve the speed with which dynamically generated pages are accessed. As a complete performance optimization scheme and performance analysis, other performance optimization techniques should still be considered.

Top-down Web cache This article first discusses the application of HTTP caching techniques to dynamic pages. When looking for a Web application optimization scenario, we should take a complete, top-down step. Fundamentally, it is important to understand the process of HTTP requests, and which specific technology to use depends on where you are. For example: Apache can be placed before your servlet is easy to accept slices, JS requests, and can also use the Fileetag command to generate the ETag response header. Use JavaScript optimization techniques, such as merging multiple JS files and removing useless information such as spaces. Use the gzip and Cache-control response headers. Use Jamonperformancemonitorinterceptor to determine the performance bottleneck in your spring application system. Make sure you fully use the ORM tool's caching mechanism so that entity information is not frequently reloaded from the database. Figuring out how to make the query cache work well takes some time. Make sure to minimize the number of data reloads in the database, especially some large lists. Large lists should be split by page, and requests for each page return a small subset of the large list. Save as little information as possible in the session. This reduces memory requirements and is useful when building an application-tier cluster. Use a Database debugging tool to determine which indexes are used when querying, and the data tables will not be locked when queried. Of course, the best maxim for performance optimization is applicable: measure two times and cut once. (After several tests and then modified) and so on, the above words are said to the carpenter, but even so, it applies to us!

A content Body ETag filter

The first way we'll see is to create a servlet filter that generates ETAG tokens based on page content (view in MVC). At first glance, using this approach does not seem to do much to improve performance. The server still needs to claim the page and adds time to calculate the tagged value. However, our goal here is to reduce the bandwidth footprint. This is a great benefit for a lot of situations where the response time is long, such as if your application's server and client are on different hemispheres of the Earth. I have seen a request from Tokyo for a server in New York, with a response of up to 350 milliseconds. This becomes a major bottleneck after considering the concurrent user factor.


Code

The technique we use to generate markup is the MD5 value that calculates what the page returns. Creating a response wrapper will do the job. The wrapper uses a byte array to hold the returned content, and after the filter chain is processed, we calculate the MD5 hash of the byte array.

The Dofilter method is implemented as follows:

Listing 1:etagcontentfilter.dofilter

public void DoFilter (ServletRequest req, servletresponse Res, Filterchain chain) throws Ioexception,servletexception {HttpServletRequest ServletRequest = (httpservletrequest) req; HttpServletResponse servletresponse = (httpservletresponse) res; Bytearrayoutputstream BAOs = new Bytearrayoutputstream (); Etagresponsewrapper wrappedresponse = new Etagresponsewrapper (Servletresponse, BAOs); Chain.dofilter (ServletRequest, wrappedresponse); byte [] bytes = Baos.tobytearray (); String token = ' "' + etagcomputeutils.getmd5digest (bytes) + '" '; Servletresponse.setheader ("ETag", token)  ; Always store the ETag in the headerstring Previoustoken = Servletrequest.getheader ("If-none-match"); If (Previou Stoken! = null && previoustoken.equals (token)) {//Compare previous token with current onelogger.debug ( "ETag match:returning 304 Not Modified"); Servletresponse.senderror (httpservletresponse.sc_not_modified);//Use the SA Me date We sent whenWe created the ETag the first time Throughservletresponse.setheader ("Last-modified", Servletrequest.getheader ("If-mo   Dified-since "));} else {//First time Through-set last modified time-to-now Calendar cal = Calendar.getinstance (); Cal.set (calend Crl Millisecond, 0);D ate lastmodified = Cal.gettime () servletresponse.setdateheader ("Last-modified", LastModified.getT IME ()); Logger.debug ("Writing body Content"); Servletresponse.setcontentlength (bytes.length);  Servletoutputstream SOS = Servletresponse.getoutputstream (); sos.write (bytes); Sos.flush (); Sos.close ();}}


It should be noted that we have set the "last-modified" response header. This is because we need a well-organized content format to correspond to clients that cannot understand the ETag response header.
The example code above uses a Etagcomputeutils tool class to produce a byte array representation of an object and process the MD5 hash logic. Here I use Javax.security.MessageDigest to calculate the MD5 value.

Listing 2:etagcomputeutils

public static byte[] Serialize (Object obj) throws IOException {byte[] byteArray = null;  Bytearrayoutputstream BAOs = Null;objectoutputstream out = null;try {//These objects is closed in the Finally.baos = new Bytearrayoutputstream (); out = new ObjectOutputStream (BAOs); Out.writeobject (obj); byteArray = Baos.tobytearray ();} Finally {if (out! = null) {Out.close ();}} return ByteArray;} public static String getmd5digest (byte[] bytes) {messagedigest Md;try {MD = messagedigest.getinstance ("MD5");} catch (NoS Uchalgorithmexception e) {throw new RuntimeException ("MD5 cryptographic algorithm is not available.", e);} byte[] messagedigest = md.digest (bytes); BigInteger number = new BigInteger (1, messagedigest);//prepend a zero to get a "proper" MD5 hash valuestringbuffer SB = n EW StringBuffer (' 0 '); sb.append (number.tostring); return sb.tostring ();}


It is very simple to call this filter in Web. xml:

Listing 3:configuration of the filter in Web.

<filter>  <filter-name> ETag Content filter </filter-name>  <filter-class> Org.springframework.samples.petclinic.web.ETagContentFilter </filter-class>  </filter>   < filter-mapping>  <filter-name> ETag Content filter </filter-name>  <url-pattern>/*.htm </url-pattern>  </filter-mapping>


Each HTM file is filtered by Etagcontentfilter, and an empty HTTP response body is returned if the file has not changed since the last request.

The methods discussed above are useful for determining the type of page, but there are some drawbacks.
After the page is generated in the server segment, we calculate the ETag value before returning to the client, and if the ETag matches, then we really do not need to remove the model data because the rendered page will not be returned to the client.
For pages where the page footer renders the date and time, each request is different, even if the page's subject content does not change.
Below, we'll look at another alternative approach--by understanding the underlying data of the build page to solve the problem with the above limitations.

ETag interceptors
The HTTP request passing path in Spring MVC includes the ability to insert an interceptor before the controller can process the request. This is an extremely appropriate entry point for inserting the etag contrast logic, where we can stop further processing if we find that the data on the build page has not changed.
The trick here is how to know that the data for the requested page has not changed. For the purposes of this article, I created a simple modifiedobjecttracker that tracks new, updated, and deleted operations by Hiberante event listeners. The tracker maintains a map for each page as a number, and a persisted entity that affects the page. If a POJO has changed, then a technology will increase the number of all pages that use this pojo. With this number as the ETag, when the client returns the ETag, we will know whether the model used by a page has changed.

Code


Starting from Modifiedobjecttracker:

Public interface Modifiedobjecttracker {     


Very simple, huh? It will be more interesting to implement. Whenever an entity changes, we update the corresponding counter for each page that uses the entity.

public void notifymodified (String entity) {//Entityviewmap is a map of entity, List of view nameslist views = Getent  Ityviewmap (). Get (entity), if (views = = null) {return;//No views is configured for the entity}synchronized (counts) {for (String view:views) {Integer count = counts.get (view); Counts.put (view, ++count);}}}


A "change" is a new, modified, or deleted operation. The following is a list of processors for the delete operation (configured as an event listener in Hibernate 3 Localsessionfactorybean).

public class Deletehandler extends Defaultdeleteeventlistener {private Modifiedobjecttracker tracker;public void OnDelete (Deleteevent event) throws Hibernateexception {Getmodifiedobjecttracker (). Notifymodified ( Event.getentityname ());} Public Modifiedobjecttracker Getmodifiedobjecttracker () {return tracker;} public void Setmodifiedobjecttracker (Modifiedobjecttracker tracker) {this.tracker = tracker;}}


The modifiedobjecttracker will be injected into the deletehandler via the spring configuration. At the same time, there will be a new and modified Saveorupdatehandler processing entity.
If the client sends back a current valid ETag (meaning that the content has not changed since the last request), we will block more processing logic to achieve our performance gains. In spring MVC, you can use a handlerinterceptoradaptor and override the Prehandle method:

Public Final Boolean Prehandle (HttpServletRequest request, httpservletresponse response, Object handler) throws Servlete Xception, IOException {String method= request.getmethod (); "GET". Equals (method)) return true; String previoustoken= request.getheader ("If-none-match"); String token= gettokenfactory (). GetToken (Request); Compare previous token with current one if (token! = null) && (Previoustoken! = null && Previoustoke N.equals (' ' + token + ') ') {response.senderror (httpservletresponse.sc_not_modified);  Re-use original Last modified timestamp response.setheader ("Last-modified", Request.getheader ("If-modified-since ")) return false; No further processing required}//Set header for the next time the client calls if (token! = null) {Response.set Header ("ETag", ' "' + token + '"); First time Through-set last modified time-to-now Calendar cal= calendar.getinstance (); Cal.set (Calendar.millisecond,); Date LasTmodified= Cal.gettime (); Response.setdateheader ("Last-modified", Lastmodified.gettime ());} return true;}


First we need to make sure that we are dealing with a GET request (the ETag can verify that the update conflicts when the client issues a put request, but that is beyond the scope of this article). If the token matches the last token returned by the server, a 304-bit change response is returned and the subsequent processing chain is bypassed. Otherwise, we set up an etag response header in case the client requests the same page the next time.

As you can see, I'm abstracting the logic that produces the markup to form an interface, so we can use different tags to generate the strategy. There is only one method for this interface:


In order to list some code less, my Sampletokenfactory implementation simultaneously undertook the etagtokenfactory task. So, we simply return the number of changes to the requested URL as a token.


That's it!

Discuss

Here, our interceptors will block all data collection and render page processing when no related data changes. Now, let's look at the HTTP header and what's going on underneath the surface. The sample program contains an introduction to the configuration that makes owner.htm use the ETag.
The first request indicates that the user has already seen the page:

http://localhost:8080/petclinic/owner.htm?ownerId=10 

get/petclinic/owner.htm?ownerid=10 http/1.1
host:localhost:8080
user-agent:mozilla/5.0 (Windows; U Windows NT 5.1; En-us; rv:1.8.1.4) gecko/20070515 firefox/2.0.0.4
accept:text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
accept-language:en-us,en;q=0.5
Accept-encoding:gzip,deflate
accept-charset:iso-8859-1,utf-8;q=0.7,*;q=0.7
keep-alive:300
Connection:keep-alive
Cookie:jsessionid=13d2e0cb63897f4edb56639e46d2bbd8
X-lori-time-1:1182364348062
if-modified-since:wed, June 18:29:03 GMT
If-none-match: "-1"

http/1.x 304 Not Modified
server:apache-coyote/1.1
date:wed, June 18:32:30 GMT

Here we trigger some changes and see if the etag changes. Added a pet to this owner:


----------------------------------------------------------
http://localhost:8080/petclinic/addPet.htm?ownerId=10

get/petclinic/addpet.htm?ownerid=10 http/1.1
host:localhost:8080
user-agent:mozilla/5.0 (Windows; U Windows NT 5.1; En-us; rv:1.8.1.4) gecko/20070515 firefox/2.0.0.4
accept:text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
accept-language:en-us,en;q=0.5
Accept-encoding:gzip,deflate
accept-charset:iso-8859-1,utf-8;q=0.7,*;q=0.7
keep-alive:300
Connection:keep-alive
referer:http://localhost:8080/petclinic/owner.htm?ownerid=10
Cookie:jsessionid=13d2e0cb63897f4edb56639e46d2bbd8
X-lori-time-1:1182364356265

http/1.x OK
server:apache-coyote/1.1
Pragma:no-cache
Expires:thu, 1970 00:00:00 GMT
Cache-control:no-cache, No-store
Content-type:text/html;charset=iso-8859-1
Content-language:en-us
content-length:2174
date:wed, June 18:32:57 GMT


----------------------------------------------------------
http://localhost:8080/petclinic/addPet.htm?ownerId=10



post/petclinic/addpet.htm?ownerid=10 http/1.1
host:localhost:8080
user-agent:mozilla/5.0 (Windows; U Windows NT 5.1; En-us; rv:1.8.1.4) gecko/20070515 firefox/2.0.0.4
accept:text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
accept-language:en-us,en;q=0.5
Accept-encoding:gzip,deflate
accept-charset:iso-8859-1,utf-8;q=0.7,*;q=0.7
keep-alive:300
Connection:keep-alive
referer:http://localhost:8080/petclinic/addpet.htm?ownerid=10
Cookie:jsessionid=13d2e0cb63897f4edb56639e46d2bbd8
X-lori-time-1:1182364402968
content-type:application/x-www-form-urlencoded
Content-length:40
Name=noddy&birthdate=1000-11-11&typeid=5
http/1.x 302 Moved temporarily
server:apache-coyote/1.1
Pragma:no-cache
Expires:thu, 1970 00:00:00 GMT
Cache-control:no-cache, No-store
location:http://localhost:8080/petclinic/owner.htm?ownerid=10
Content-language:en-us
content-length:0
date:wed, June 18:33:23 GMT

Because we did not configure the ETag for addpet.htm, the associated response header is not set. Now, once again, we visit Owener 10, noting that the etag in the corresponding becomes 1:


----------------------------------------------------------
http://localhost:8080/petclinic/owner.htm?ownerId=10

 get/petclinic/owner.htm?ownerid=10 http/1.1
 host:localhost:8080
 user-agent:mozilla/ 5.0 (Windows; U Windows NT 5.1; En-us; rv:1.8.1.4) gecko/20070515 firefox/2.0.0.4
 accept:text/xml,application/xml,application/xhtml+xml,text/ html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
 accept-language:en-us,en;q=0.5
  Accept-encoding:gzip,deflate
 accept-charset:iso-8859-1,utf-8;q=0.7,*;q=0.7
 keep-alive:300
 connection:keep-alive
 referer:http://localhost:8080/petclinic/addpet.htm?ownerid=10
 cookie:jsessionid=13d2e0cb63897f4edb56639e46d2bbd8
 x-lori-time-1:1182364403109
  if-modified-since:wed, June 18:29:03 GMT
 if-none-match: "1"

http/1.x OK
server:apache-coyote/1.1
Etag: "1"
last-modified:wed, June 18:33:36 GMT
Content-type:text/html;charset=iso-8859-1
Content-language:en-us
content-length:4317
date:wed, June 18:33:45 GMT

Finally, we again request Owener 10, this etag played a role, we received a 304 unchanged information.

----------------------------------------------------------
http://localhost:8080/petclinic/owner.htm?ownerId=10

 get/petclinic/owner.htm?ownerid=10 http/1.1
 host:localhost:8080
  user-agent:mozilla/5.0 (Windows; U Windows NT 5.1; En-us; rv:1.8.1.4) gecko/20070515 firefox/2.0.0.4
 accept:text/xml,application/xml,application/xhtml+xml,text/ html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
 accept-language:en-us,en;q=0.5
  Accept-encoding:gzip,deflate
 accept-charset:iso-8859-1,utf-8;q=0.7,*;q=0.7
 keep-alive:300
 connection:keep-alive
 cookie:jsessionid=13d2e0cb63897f4edb56639e46d2bbd8
  X-lori-time-1:1182364493500
 if-modified-since:wed, June 18:33:36 GMT
 if-none-match: "1"

 http/1.x 304 Not Modified
 server:apache-coyote/1.1
 date:wed, June 18:34:55 GMT

Thus, we use HTTP caching to reduce bandwidth usage and shorten processing cycles.
The Fine Print: In fact, more granular object change tracking, such as the use of object identifiers, is used. can increase efficiency more. However, the association between pages and entities is largely determined by the design of the data model in the system. The above implementation (Modifiedobjecttracker) is an illustrative example, and the answer is to provide ideas for a more in-depth attempt. The purpose of the above implementation is not to apply to the actual production environment (for example, not for a clustered environment), and a further consideration is to use the database trigger to track data changes and have the Interceptor monitor the data table where the trigger outputs the results.

Conclusion

We have seen two ways to use the etag to reduce loan occupancy and shorten processing cycles. What I want is this article provides a way for you to present and future Web application projects, as well as the correct understanding and use of the underlying ETag response header.
As Newton said, "If I look farther, it is because I stand on the shoulders of giants." As the core of rest, this style of application speaks of simple, elegant software design that does not reinvent the wheel. I believe that the core of understanding and using restful architecture is a good development of mainstream application development, and I look forward to being able to lift it up in future development.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.