优化网站设计(十四):使AJAX调用尽可能利用缓存特性
前言
网站设计的优化是一个很大的话题,有一些通用的原则,也有针对不同开发平台的一些建议。这方面的研究一直没有停止过,我在不同的场合也分享过这样的话题。
作为通用的原则,雅虎的工程师团队曾经给出过35个最佳实践。这个列表请参考 Best Practices for Speeding Up Your Web Site http://developer.yahoo.com/performance/rules.html,同时,他们还发布了一个相应的测试工具Yslow http://developer.yahoo.com/yslow/
我强烈推荐所有的网站开发人员都应该学习这些最佳实践,并结合自己的实际项目情况进行应用。 接下来的一段时间,我将结合ASP.NET这个开发平台,针对这些原则,通过一个系列文章的形式,做些讲解和演绎,以帮助大家更好地理解这些原则,并且更好地使用他们。
准备工作
为了跟随我进行后续的学习,你需要准备如下的开发环境和工具
- Google Chrome 或者firefox ,并且安装 Yslow这个扩展组件.请注意,这个组件是雅虎提供的,但目前没有针对IE的版本。
- https://chrome.google.com/webstore/detail/yslow/ninejjcohidippngpapiilnmkgllmakh
- https://addons.mozilla.org/en-US/firefox/addon/yslow/
- 你应该对这些浏览器的开发人员工具有所了解,你可以通过按下F12键调出这个工具。
- Visaul Studio 2010 SP1 或更高版本,推荐使用Visual Studio 2012
- 你需要对ASP.NET的开发基本流程和核心技术有相当的了解,本系列文章很难对基础知识做普及。
本文要讨论的话题
这一篇我和大家讨论的是第十四条原则:Make Ajax Cacheable (使AJAX调用尽可能利用缓存特性)。
AJAX的基本概念
- AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
- AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
- AJAX 是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。
AJAX的典型应用场景
AJAX在目前的应用程序中使用非常广泛,为网站提供了更加丰富的效果(虽然技术很早就有,但最早引起大家注意是在2004年左右的Gmail中)。其典型的应用场景包括
- 异步加载,使得页面的内容可以分批加载。
- 局部更新,使得页面的局部更新不会导致页面的刷新。
由于AJAX其实也是需要发起请求,然后服务器执行,并将结果(通常是JSON格式的)发送给浏览器进行最后的呈现或者处理,所以对于网站设计优化的角度而言,我们同样需要考虑对这些请求,是否可以尽可能地利用到缓存的功能来提高性能。
【备注】关于AJAX,以及它与目前的一些技术(主要是服务器端的技术)如何结合的文档,我之前写过很多,有兴趣的朋友可以先参考一下 http://www.google.ee/search?q=site%3Awww.cnblogs.com%2Fchenxizhang%2F%20ajax
什么样的AJAX请求可以被缓存?
对服务器请求进行优化的方法有很多,我之前已经写过几篇,这些原则也可以应用在AJAX的场景中
- 优化网站设计(三):对资源添加缓存控制
- 优化网站设计(四):对资源启用压缩
- 优化网站设计(九):减少DNS查找的次数
- 优化网站设计(十):最小化JAVASCRIPT和CSS
- 优化网站设计(十一):避免重定向
- 优化网站设计(十三):配置ETags
但是,对于AJAX而言,有一些特殊性,并不是所有的AJAX请求都是可以缓存的。这是由于AJAX的请求通常有两种不同的方法:POST和GET。他们在进行请求的时候,就会略有不同。
- POST的请求,是不可以在客户端缓存的,每次请求都需要发送给服务器进行处理,每次都会返回状态码200。(这里可以优化的是,服务器端对数据进行缓存,以便提高处理速度)
- GET的请求,是可以(而且默认)在客户端进行缓存的,除非指定了不同的地址,否则同一个地址的AJAX请求,不会重复在服务器执行,而是返回304。
针对POST的情况如何优化
POST的请求,浏览器通常会假定用户是想要提交(或者发送)数据给服务器,既然如此,那么浏览器自然就不会对该请求进行缓存,因为你是提交数据,所以它认为服务器自然每次都是需要处理的。我们可以来看一个例子。
using System; using System.Web.Services; namespace WebApplication4 { /// <summary> /// Summary description for HelloWebService /// </summary> [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. [System.Web.Script.Services.ScriptService] public class HelloWebService : System.Web.Services.WebService { [WebMethod] public string HelloWorld() { return string.Format("Hello,world -- {0}", DateTime.Now); } } }
上面是一个简单的XML Web Service的定义。需要注意的是,如果希望支持AJAX访问的话,必须要添加ScriptService这个Attribute。
我们的调用代码如下:
//XML Web Service只支持POST,这种方式无法在浏览器中缓存,但可以结合服务器端的缓存,减少后台代码执行的次数来提高性能 $("#btCallXMLWebService").click(function () { $.ajax({ type: "POST", contentType: "application/json;utf-8", url: "HelloWebService.asmx/HelloWorld", data: null, dataType: "json", success: function (result) { alert(result.d); } }); });
运行起来之后,我们多次点击这个按钮,会截获到如下的请求:
根据上面的截图不难看出,其实每次都请求都是重新被处理过的,它们都是返回状态码为200。
这就是POST AJAX请求的处理情况,它不会被客户端缓存。那你可能会说,能不能将type改为GET呢?例如下面这样
$("#btCallXMLWebService").click(function () { $.ajax({ type: "GET", contentType: "application/json;utf-8", url: "HelloWebService.asmx/HelloWorld", data: null, dataType: "json", success: function (result) { alert(result.d); } }); });
答案是,针对XML Web Service或者标准的WCF服务,它们不支持通过GET进行请求,只支持POST请求。
服务器返回了状态码为500的错误,并且在正文里面描述了这个错误的信息,如下图所示
那么,针对这种场景,我们是否有什么方法进行优化呢?
using System; using System.Web.Services; namespace WebApplication4 { /// <summary> /// Summary description for HelloWebService /// </summary> [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. [System.Web.Script.Services.ScriptService] public class HelloWebService : System.Web.Services.WebService { /// <summary> /// 该方法被缓存了10秒钟,是将结果缓存在服务器内存中。 /// </summary> /// <returns></returns> [WebMethod(CacheDuration=10)] public string HelloWorld() { return string.Format("Hello,world -- {0}", DateTime.Now); } } }
这样修改之后,对于客户端而言,其实没有什么改变的,多次调用的时候,服务器都需要处理,然后返回状态码为200。但是区别是什么呢?区别在于服务器并不需要每次都运行真正的代码,它将结果缓存在内存中,在10秒之内重复调用,就直接返回该内存中的数据即可。(这样可以提高服务器的性能,提高并发性)
【备注】如果是WCF来做服务的话,默认是不支持直接对操作进行缓存的。
如何设计支持GET的服务
我们了解到默认情况下,XML Web Service和WCF服务,都只支持使用POST方法的AJAX调用。那么是否有办法设计出来一个支持GET的服务呢?
- XML Web Service不支持GET
- WCF服务,可以通过一个特殊的webHttpBinding支持GET。本文将讨论这一种做法。
【备注】WCF有多种适用场景,我之前写过两篇文章,有兴趣的朋友可以参考
WCF技术的不同应用场景及其实现分析
WCF技术的不同应用场景及其实现分析(续)
3. ASP.NET MVC中可以支持Web API这个功能,可以通过GET的方式进行调用。这个的做法,本文不做探讨,有兴趣的朋友可以参考 http://www.asp.net/web-api
我们来看一个例子。在WCF中支持一种特殊的Operation,就是WebGet
using System; using System.ServiceModel; using System.ServiceModel.Activation; using System.ServiceModel.Web; namespace WebApplication4 { [ServiceContract(Namespace = "")] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class HelloWCFService { [WebGet(ResponseFormat = WebMessageFormat.Json)] public string RestfulHelloWorldWithParameter(string name) { return string.Format("Hello,{0} -- {1}", DateTime.Now, name); } } }
AJAX调用代码如下
$("#btCallRestfulWCFServicebwithParameter").click(function () { var name = $("#txtName").val(); //GET请求默认就是会被缓存(在同一个浏览器中,默认是临时缓存,浏览器一关闭就删除掉) $.getJSON("HelloWCFService.svc/RestfulHelloWorldWithParameter", { name: name }, function (data) { alert(data.d); }); });
运行起来之后,我们分别输入不同的name参数,并且分别调用两次。
我们可以发现,第一次调用会返回状态码(200),而第二次调用会返回状态码(304),但如果参数不一样,又会返回状态码(200)。依次类推。
我们也确实可以在浏览器缓存中找到两份缓存的数据
所以对于GET请求,默认就会被缓存。但是,如果你想改变这个行为,例如你有时候不想做缓存,应该如何来实现呢?
避免对GET请求做缓存
有的时候,我们可能希望GET请求不被缓存,有几种做法来达到这样的目的。
- 每次调用的时候,请求不同的地址(可以在原始地址后面添加一个随机的号码)例如下面这样:
$("#btCallRestfulWCFServicebwithParameter").click(function () { var name = $("#txtName").val(); //GET请求默认就是会被缓存(在同一个浏览器中,默认是临时缓存,浏览器一关闭就删除掉) $.getJSON("HelloWCFService.svc/RestfulHelloWorldWithParameter", { name: name,version:Math.random() }, function (data) { alert(data.d); }); });
- 如果你所使用的是jquery的话,则可以考虑禁用AJAX的缓存
$.ajaxSetup({ cache: false });
使用HTML5的新特性来减少不必要的AJAX调用
我觉得一个比较彻底的做法是,考虑将一部分数据缓存在客户端中,而且最好不要在临时文件夹中,以便下次启动时还能使用到这些数据。HTML 5中提供了一个新的特性:local storage,可以很好地解决这个问题,如果有兴趣的朋友可以参考下面的文档