日期:2014-05-16  浏览次数:20623 次

使用ssh开发rest web服务支持http etag header的教程详解

原创整理不易,转载请注明出处:使用ssh开发rest web服务支持http etag header的教程详解

代码下载地址:http://www.zuidaima.com/share/1777391667989504.htm


导言

REST方式的应用程序构架在近日所产生的巨大影响突出了Web应用程序的优雅设计的重要性。现在人们开始理解“WWW架构”内在的可测量性及弹性,并且已经开始探索使用其范例的更好的方式。在本文中,我们将讨论一个Web应用开发工具——“简陋的、卑下的”ETags,以及如何在基于SpringFramework的动态Web应用程序中集成这个工具,来提高应用的性能及可测性。

我们将要使用的基于Spring的应用程序是基于“petclinic”(宠物门诊?)的一个应用。在您下载的程序包中,包含了如何加入必要的配置和源代码让你亲自体验该程序的介绍。

什么是ETag
 
在HTTP协议规范中,ETag被定义为“被请求的变量的实体值”。(
参见 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - Section 14.19。)换句话说,ETag是一个与Web资源相关联的标记。典型的Web资源是一个Web页面,但也可以是一个JSON格式或者XML格式的文档。服务器可以指出一个标记是什么及其意义,并将这个标记放在HTTP头重传送给客户端。

ETag如何提高应用程序性能

ETag和一个GET请求的“If-None-Match”头信息一起使用,服务器开发者以此来使用客户端缓存的优势。服务器在客户端的一次请求时产生ETag,并在以后的请求中判断被请求资源是否发生了变化。确切的说,客户端将这个标记传回给服务器,来验证它自己的缓存是否有效。

整个处理过程如下:

客户端请求页面A
服务器响应,返回页面A,附加ETag
客户端显示A,并将页面和ETag一并缓存
客户端再次请求页面A,请求中包含了上次请求页面A时返回的ETag
服务器检查客户端发送过来的ETag,并确定页面A在该客户端上次请求后到现在没有发生过变化,因此,发送一个304(未改变)响应头给客户端,附带一个空的响应体。

文章的剩余部分将讨论在基于SpringFramework的使用SpringMVC的Web应用程序中使用ETag两种方式。首先,我们将通过一个Servlet2.3 过滤器,使用由计算请求返回结果的MD5值而产生的ETag(一个简单的ETag实现)。第二种方式使用一种更加“专业”的方式通过跟踪页面呈现所用到的模型的变化来确定ETag的有效性(一个“专业”的ETag实现)。虽然我们在这里使用了Spring MVC,但这个技术适用于其他任何的MVC框架。

在继续之前,我们有必要明确,ETag技术是为了希望改进动态产生的页面的访问速度而提出的。作为一个完整的性能优化方案和性能分析,其他的性能优化技术依然应当被考虑。
 

自顶向下的Web缓存 本文首先讨论将HTTP缓存技术应用于动态页面。寻求Web应用程序优化方案时,我们应当采用一个完整的,自顶向下的步骤。从根本上说,理解HTTP请求的过程是很重要的,采用哪种具体的技术取决于你在什么场合。例如: Apache可以放在你的Servlet容易之前,来接受如图片,js请求,同时也可以使用FileETag指令产生ETag响应头。 使用Javascript优化技术,例如将多个js文件合并,并去除空格等无用信息。 利用GZip和Cache-Control响应头。 使用JamonPerformanceMonitorInterceptor确定你的Spring应用系统中的性能瓶颈。 确定你充分地使用了ORM工具的缓存机制,从而使得实体信息不是频繁的从数据库中重新加载。搞清楚如何让查询缓存很好的工作需要一定的时间。 确保尽量少聪数据库中重新加载数据,特别是一些大的列表。大列表应当被按页分割,对每一页的请求返回大列表的一个小的子集。 Session中保存尽量少的信息。这降低了内存要求,在建立应用层集群时将会显得非常有用。 使用一个数据库调试工具,确定查询时使用了哪些索引,查询时数据表将不会被锁定。 当然了,性能优化的最佳格言是适用的:测量两次,切割一次。(多次测试后再修改) 等等,上面的话是对木匠说的,但虽然如此,它一样适用于我们!

 

 一个内容主体ETag过滤器

我们将看到的第一种方式是建立一个Servlet过滤器基于页面内容(MVC中的View)来产生ETag标记。乍一看,使用这种方式对性能的提升似乎没什么大的作用。服务器依然需要声称页面,并且增加了计算标记值的时间。但是,在这里我们的目的是减少带宽占用。这对于很多的反应时间很长的情形是一个很大的益处,例如如果你的应用的服务器和客户端分别在地球的不同半球上。我曾看到一个从东京发出的对纽约的某台服务器的请求,响应长达350毫秒。考虑并发用户因素后,这将成为一个重大的瓶颈。


代码

我们用于产生标记的技术是计算页面返回内容的MD5值。创建一个响应包装器将完成这个工作。包装器使用一个字节数组来保存返回内容,在过滤器链处理完成之后,我们计算这个字节数组的MD5哈希值。

doFilter方法的实现如下:

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 header

	String previousToken  =  servletRequest.getHeader( " If-None-Match " );
	if  (previousToken  !=   null   &&  previousToken.equals(token))  {  //  compare previous token with current one
	logger.debug( " ETag match: returning 304 Not Modified " );
	servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);
	//  use the same date we sent when we created the ETag the first time through
	servletResponse.setHeader( " Last-Modified " , servletRequest.getHeader( " If-Modified-Sin