技术开发 频道

ASP.NET性能提升之浏览器缓存的调用

  【IT168 专稿】 

  浏览器中进行的缓存 AJAX调用

  浏览器能够在用户的硬盘驱动器上缓存图片、JavaScript、CSS文件,并且如果该调用是一个HTTP  GET请求,它也能够缓存XML HTTP调用,缓存基于URL。如果是相同的URL,则会缓存到计算机上,然后当再次请求时从缓存中加载响应,而不是从服务器上加载。基本上,浏览器能够缓存调用的任何HTTP GET并基于URL返回缓存数据。

  第一篇:ASP.NET性能提升秘诀之管道与进程优化 

  第二篇:ASP.NET性能提升之站点部署与内容传输

  如果以HTTP GET的方式进行一次XML HTTP的调用,服务器则会返回一些特殊的头,并告知浏览器缓存这些响应,在将来调用时,响应便立即从缓存中返回,因此节约了网络之间的往返和数据的下载时间。

  在Pageflakes站点上,我们缓存了用户的状态以便当用户第二天再次访问站点,用户从浏览器缓存中获得一个加载实例的缓存页面,二不是从服务器上获取。因此第二次加载会变得非常快。我们也缓存了很多页面上的小部分,这些部分出现在用户的活动中。当用户再次执行相同活动时,缓存的内容就会从本地缓存中立即加载,因此节约了网络带宽时间。用户在站点上获得了更快的速度并得到了快速的响应。会感觉到速度明显有所增加。

  这样做是为了当对Atlas Web服务调用时进行HTTP GET调用并返回一些指定的HTTP响应头以告知浏览器缓存这些响应一段时间。如果在响应期间返回期满头,浏览器将缓存XML HTTP响应。这里有两种你需要返回的头来响应:

  HTTP/1.1 200 OK
  Expires: Fri,
1 Jan 2030
  Cache
-Control: public

  这表示浏览器缓存响应到2030年1月。只要你使用相同的参数发出相同的XML HTTP调用,你将可以从计算机上获得缓存的响应并且不会调用源服务器。这里有更多的高级方式来获得响应缓存的控制。例如,虽然这里头指示出浏览器将缓存60秒,但是60秒后会连接到服务器并获得一个刷新的响应。当浏览器本地缓存期满60秒后,系统也会阻止代理返回缓存响应。

HTTP/1.1 200 OK
  Cache
-Control: private, must-revalidate, proxy-revalidate, max-age=60

  咱们试着从ASP.NET Web服务调用产生这种响应头:

[WebMethod][ScriptMethod(UseHttpGet=true)]
public string CachedGet()
{
    TimeSpan cacheDuration
= TimeSpan.FromMinutes(1);
    Context.Response.Cache.SetCacheability(HttpCacheability.Public);
    Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
    Context.Response.Cache.SetMaxAge(cacheDuration);
    Context.Response.Cache.AppendCacheExtension(
          
"must-revalidate, proxy-revalidate");
    
return DateTime.Now.ToString();
}

  下面的响应头中展示了这一结果: 



  虽然期满头进行了正确的设置,但是问题出在了Cache-control。Max-age设置为0表示系统将会阻止浏览器进行各种缓存操作。如果你确实想要阻止缓存,你应该发生这种缓存控制头。

  该输出是不正确的,并且没有缓存:

  ASP.NET 2.0中出现的一个bug就是不能改变max-age头。当max-age设置为0时,ASP.NET 2.0会设置Cache-control为私有,因为max-age= 0意味着不需要缓存。因此,没有办法能够使得ASP.NET 2.0返回缓存响应的头。这是由于ASP.NET AJAX框架对Web服务调用进行了拦截并在执行一个请求之前,错误地将max-age设置为0作为默认值。

  反编译HttpCachePolicy类的代码 (Context.Response.Cache对象的类),我发现了如下的代码:


                   
  this._maxAge 设置为0,然后检查"if(!this._isMaxAgeSet || (delta < this._maxAge))"以阻止被设置为更大的值。由于这个问题,我们需要传递SetMaxAge 函数并使用反射直接设置_maxAge字段的值。

[WebMethod][ScriptMethod(UseHttpGet=true)]
public string CachedGet2()
{
    TimeSpan cacheDuration
= TimeSpan.FromMinutes(1);
    FieldInfo maxAge
= Context.Response.Cache.GetType().GetField("_maxAge",
        BindingFlags.Instance
|BindingFlags.NonPublic);
    maxAge.SetValue(Context.Response.Cache, cacheDuration);
    Context.Response.Cache.SetCacheability(HttpCacheability.Public);
    Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
    Context.Response.Cache.AppendCacheExtension(
            
"must-revalidate, proxy-revalidate");
    
return DateTime.Now.ToString();
}

  将会返回下面的头:


 
  现在max-age设置成了60,因此浏览器将缓存响应60秒。如果你在60秒内进行相同的再次调用,则会返回相同的响应。这里的测试输出展示了从服务器上返回的时间:
 
  一分钟以后,缓存期满同时浏览器再次向服务器发送请求调用。其客户端代码如下:

function testCache()
{
    TestService.CachedGet(function(result)
    {
        debug.trace(result);
    });
}

  另外一个问题解决了。在web.config文件中,你会看到ASP.NET Ajax添加了如下节点值:

<system.web>
<trust level="Medium"/>

  这可以阻止我们设置Response对象的_maxAge字段,因为它需要反射。因此,你不得不删除这一信任级别或者将其放置为Full。

<system.web>
<trust level="Full"/>

  充分利用浏览器缓存
  
  ·持续使用URLs  
  
  基于 URL的浏览器缓存内容。当URL变化时,浏览器会从源服务器拿取一个新的版本。URL可以通过改变查询字符串参数来改变。例如,/default.aspx 被缓存到了浏览器上。如果你请求/default.aspx? 123,则系统会重新从服务器上去拿一个新版本。如果你返回正确的缓存头,则来自新的URL响应也能够缓存到浏览器中。如果那样的话,改变一些查询字符串参数比如像/default.aspx ?456将会从服务器上返回新的内容。

  因此,你需要确保当你需要获得缓存响应的时候在每个地方使用持续这个URL。从主页上,如果你请求了一个URL为/welcome.gif的文件,确保从另一个页面你使用了相同的URL进行了请求。一个常见的错误就是有时会从URL的子域“www”进行发射。
  www.pageflakes.com/default.aspx和pageflakes.com/default.aspx是不同的,两者都被单独进行了缓存。

  ·缓存静态内容更长时间

  可以静态文件缓存更长的时间,比如1个月。如果你正在思考你应该将其缓存两天以便当你改变文件时,用户可以得到更快的响应,这样看你就错了。如果你更新了一个通过缓存头的文件,新用户将会立即获得新的文件而之前的用户将仍然看到的是更新前的内容直到浏览器上的期满日期。因此,只要你使用了期满头来缓存静态内容,你就应该为它分配一个尽可能高的值。

  例如,如果你设置期满头缓存一个文件为三天,一个用户今天获得了一份该文件并且在接下来的三天都存储在缓存中。另一个用户明天将获得这个文件然后在明天后的三天缓存它。如果你后天改变了这个文件,则第一个用户将在第四天看到它而第二个用户将在第五天看到它。因此,不同的用户将看到不同版本的文件。由此,设置的值较低并没有多大帮助。你将不得不改变文件的URL以便确保每个人都能及时地拿到实际相同的最新文件。

  你可以从IIS管理器总配置静态文件的期满头。你将会在后面部分了解到如何实现。

  ·使用缓存友好的文件夹结构

  将缓存内容存储在一个普通的文件夹下。例如,将你站点上的所有图片文件存储在/static文件夹下而不是单独存储在不同的子文件夹下。这将帮助你使用持续的URL贯穿整个站点,因为从站点的任何地方你都能够使用/static/images/somefile.gif。后面,当你的静态缓存文件位于一个共通的跟文件夹下时,我们将学习一种更简单的方式来将文件移动到一个内容传递网络中。

  ·重用共通的图像文件
 
  有时候我们需要放置一些共通的图像文件到多个虚拟目录下以便我们能够写些更小的路径。例如,假如在根文件夹中有一个indicator.gif 文件,以及一些子文件夹和CSS文件夹。你会很放心因为你不需要担心来自不同地方的路径并且你仅仅需要使用文件的名称即可作为相对URL。在缓存中,这并没有多大帮助。每个文件的副本都被单独地缓存到浏览器中了。因此,你应该在整个解决方案中聚集所有的图像文件并放置它们到相同的静态文件夹下面来减少这种复制,进而达到对所有的页面和CSS文件使用相同的URL。

  ·当你想要缓存期满时,改变文件名称

  当你想要改变一个静态文件时,不要仅仅只是更新文件,因为它已经缓存到用户的浏览器中了。你需要改变文件名称并更新所有与之相关的引用以便浏览器下载新的文件。你也可以存储文件名到数据库中或者配置文件中并使用数据绑定来动态产生URL。这种方式使你可以从一个地方改变URL并且使得整个站点立即接收到这种改变。

  ·访问静态文件时,使用一个版本号

  如果你不想将你的相同文件存储多个副本到你的静态文件夹中,你可以使用查询字符串来区别相同文件的不同版本。例如,一个像like /static/images/indicator.Gif? v=1的查询字符串用来访问一个GIF格式的图片。当你改变indicator.gif的时候,你可以重写相同的文件然后更新所有到文件/static/images/indicator.gif?v=2的引用,这种方式可以保持相同文件的不断变化并仅仅使用新的版本号更新所引用的图像.

   ·在不同的域中存储缓存文件

  将静态内容放置到不同的域中确实是一个好办法。首先,浏览器能够打开其它两个并发的链接来下载静态文件。另一个好处就是你不需要发送cookies到静态文件上。当你放置静态文件到相同的域上作为你的Web应用程序时,浏览器会发送所有的ASP.NET cookies以及你Web应用程序上生成的所有其它cookies。这会使得请求大变得必要的大并且还浪费带宽。你不需要发送这些cookies来访问静态文件。因此,如果你放置了静态文件在一个不同的域中,这些cookies将 不会发送。例如,放置你的静态文件到www.staticcontent.com域中而你的网站正运行在www.dropthings.com上。其它域并不需要与网站的完全 不同。它仅仅是一个别名并且共享相同的Web应用程序路径。

  ·SSL不能进行缓存,因此尽量少使用

  任何以SSL方式提供的内容都不会被缓存。因此,你需要将静态内容放置到SSL的外部。此外,你应该试着限制SSL仅仅用于一些安全页面,诸如登录页面或者付款页面。站点重置应该作为跨规则HHTP的外部SSL。SSL加密请求和响应会为服务器增加额外的负载。加密后的内容也比加密前的内容要大,因此会更耗费带宽。
HTTP POST 请求不能缓存
 
  缓存仅仅针对HTTP GET请求才会发生。HTTP POST请求不会进行缓存。因此,你想要实现的任何AJAX调用要达到缓存都需要使用HTTP GET这种方式。

  ·生成Content-Length 响应头

  当你通过Web服务调用或者HTTP handlers动态提供内容时,确保你发射了 Content-Length 头。这样浏览器会以更快的速度下载内容,因为它根据查找Content-Length 头从响应中知道下载了多少字节。当这个头存在时,浏览器能够进行更加持续的链接。这样节约了浏览器需要为每个请求建立一个新链接所花费的时间。当没有Content-Length 头时,浏览器不知道它将从服务器上获得多少字节,因此只要从服务器上接收到字节便会一直保持链接打开直到链接关闭为止。因此,你损失了很多因为下载很多小文件诸如CS, JavaScripts以及图片所带来的时间。

  ·如何在IIS中配置静态内容 
 
  在IIS管理器中,你可以在网站属性对话框中的“HTTP头”标签中为所有IIS处理的请求定义期满头。在这里你可以定义是否期满内容立即执行或者某些天后期满或者某个具体的日期期满。第二个选项(期满后)使用相对期满日期,不使用绝对期满日期。这是非常有用的,因为每个请求它都会工作。无论是用户请求一个静态文件,IIS都会基于从期满后的天/月数计算期满日期。


                 
  对于由ASP.NET提供的动态页面,handler能够修改期满handler并重写IIS的默认设置。

0
相关文章