返回
软件
分类

我们都知道浏览器会缓存访问过网站的网页,响应包含了从服务器返回的HTTP载荷中的数据

日期: 2019-11-19 17:51 浏览次数 : 190

BKJIA.com 5月18日外电头条】而过去两周里,我一直在寻找一些能够进一步提高UI性能的方法,得到的成果主要是返回正确的HTTP状态代码,优化浏览器的缓存功能。

我们都知道浏览器会缓存访问过网站的网页,浏览器通过URL地址访问一个网页,显示网页内容的同时会在电脑上面缓存网页内容。如果网页没有更新的话,浏览器再次访问这个URL地址的时候,就不会再次下载网页,而是直接使用本地缓存的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。 

Ajax 是一种编程技术,它使用 XML、JavaScript 和 Web 标准来创建高度交互性的 Web 页面,正如您在 Google Maps 和大量其他站点上所看到的页面那样。本文全面介绍了两个简单的 Ajax 例子,延着这个思路介绍了 Ruby/Ajax 这一组合如此成功的原因。

BKJIA推荐专题:Ruby On Rails开发教程

一、什么是HTTP Cache 

Ajax 定义

具体地说,会返回两种状态代码:

对于浏览器的这种网页缓存机制大家已经耳熟能详了,举个例子来说,JavaEye的新闻订阅地址: , 当浏览器或者订阅程序访问这个URL地址的时候,JavaEye的服务器在response的header里面会发送给浏览器如下状态标识: 

Ajax 代表 Asynchronous JavaScript + XML。信息架构师 Jesse James Garrett 于 2005 年提出这一术语,该术语用来描述一门在夹缝中生存了近二十年的技术。Ajax 的使用随即爆增,不论在图书馆、流行网站还是文献作品中都保持同步增长。

◆ 返回200-“Ok”,这告知浏览器服务器能够成功的对请求进行响应。响应包含了从服务器返回的HTTP载荷中的数据。

C代码 

Ajax 重新定义了基本的浏览器使用模型。原模型一次呈现一个页面。Ajax 允许浏览器在页面更新的间隔同服务器进行交流。这样做的好处是带来更加丰富的客户体验,但却以增加复杂度为代价。Ajax 是这样运行的:使用 JavaScript 客户端库在客户机和服务器间发送 XML。Ajax 开发人员可以在任何时刻从客户机发送异步请求,因而在服务器处理这些请求时,用户交互可以继续进行。下面就是 Ajax 请求的流程:

◆ 返回304-“Not modified”,表示未修改,这告知浏览器所发出请求中的数据并没有改变,因此可以从缓存中装载数据。这种情况下,响应不包含HTTP载荷。

 必赢备用网址 1)

  1. 一个事件(如用户的鼠标点击或编程计时器的触发)启动一个 JavaScript 函数。
  2. JavaScript 函数为部分页面而不是整个页面创建一个请求。JavaScript 随后通过 HTTP 将该请求发送到 Web 服务器。
  3. 此 HTTP 请求调用服务器上的一个脚本,如 Rails 控制器方法或 Java™ servlet。
  4. 该服务器脚本创建一个 XML 文档并将其返回给服务器。
  5. 在接收结果的同时,客户机异步处理创建、更新或删除部分 Web 页面,如列表元素、div 标记或图像。

既然“Not modified”消息包含的数据要少得多(没有内容,只有头),这样你就最好返回到浏览器这里,当然你需要先确保浏览器的缓存中已经存在数据了。

  1. Etag    "427fe7b6442f2096dff4f92339305444"  
  2. Last-Modified   Fri, 04 Sep 2009 05:55:43 GMT  

所有 Ajax 应用程序都使用类似这种顺序的一种方法。例如,某个应用程序允许将字典中的单词与其定义一起保存。旧式的应用程序会强迫您用一个新的页面视图来编辑定义。Ajax 允许原地编辑,它用一个条目字段替换定义文本,然后用更新的定义来替换该表单。

在我的应用中,我发现服务器返回的200-响应比304-响应要多得多。这造成了两方面的问题:

这就是告诉浏览器,新闻订阅这个网络资源的最后修改时间和Etag。于是浏览器把这两个状态信息连同网页内容在本地进行缓存,当浏览器再次访问JavaEye新闻订阅地址的时候,浏览器会发送如下两个状态标识给JavaEye服务器: 

Ajax 解决方案的组件是:

◆ 不得不传输比所需的更多的数据

C代码 

  • 客户端 JavaScript 库,用来管理异步请求。
  • 服务器端 JavaScript 库,用来处理进来的请求,并构造一个 XML 响应。
  • 客户端 JavaScript 库,用来处理生成的 XML。
  • 称作文档对象模型(DOM)的库,允许对现有 Web 页面进行更新。
  • 辅助例程,用来处理不可避免的 UI 和集成问题。

◆ UI不得不处理更多数据

 必赢备用网址 2)

事件/请求/响应/替换模型是大多数 Ajax 应用程序的核心模型,但如果您刚接触 Ajax,您一定会对 Ajax 中大量的可用库和这些库之间巨大的差别感到惊讶不已。该领域中有许多 Ajax 框架,它们的功能常常重叠且没有确定的胜出者。单就 Java 市场而言,有许多库可用,包括 Echo、Dojo、DWR、Google Web Toolkit(GWT)、Java Web Parts、AjaxAnywhere、AjaxTags、Scriptaculous 和 Prototype。这些框架使用截然不同的方法。一些框架试图通过生成 JavaScript 代码的 Java 库来隐藏 JavaScript,如 GWT。另一些框架致力于使 JavaScript 更易使用。一些相当地全面,如 Dom4J,而另一些则仅着力于解决好一个小问题。由于有许多流行的新技术,解决方案之间互相割据的场面有时会很难驾驭,调试工具、UI 实践(如 Back 按钮)和明智的开发实践的实现非常缓慢。Java 平台上的 Ajax 库的力量源自其多样性。这也正是其缺点所在,因为多样性导致了难以决断、集成方面的顾虑和复杂性。

这两方面出现的问题都会让应用的速度变慢。虽然只是慢了一点,但在UI端还是足够让人察觉到了。幸运的是你只需要对Rails应用做几个小修改,就能获得应有的效果。

  1. If-None-Match   "427fe7b6442f2096dff4f92339305444"  
  2. If-Modified-Since   Fri, 04 Sep 2009 05:55:43 GMT  

有了 Ruby on Rails,开发体验就显著不同了,这是由于两个原因。首先,Ruby on Rails 有一个核心的 Web 开发平台:Ruby on Rails。其次,到目前为止,大多数在 Rails 上的 Ajax 开发体验都围绕着两个核心框架:Scriptaculous 和 Prototype。Rails 方法使用运行时代码生成和定制标记,这使您不必理会复杂的 JavaScript。是时候自己来实践了。如果您想要在学习本文的过程中编写代码的话,需要下载 Rails,也要下载必要的 Ajax 框架。打开您的 Rails 环境,跟我一起来吧。

1在GET方法中使用stale?语句

就是告诉服务器,我本地缓存的网页最后修改时间和Etag是什么,请问你服务器的资源有没有在我上次访问之后有更新啊?于是JavaEye服务器会核对一下,如果该用户上次访问之后没有更新过新闻,那么根本就不必生成这个RSS了,直接告诉浏览器:“没什么新东西,你还是看自己缓存的网页吧”,于是服务器就发送一个304 Not Modified的消息,其他什么都不用干了。 

没有Ajax 的简单的 Rails 应用程序

def show    @list_item = @list.list_items.find( params[ :id ] )    if stale?( :etag => @list_item, :last_modified => @list_item.updated_at.utc, :public => true )      respond_with( @list_item )    end  end  

这就是HTTP层的Cache,使用这种基于资源的缓存机制,不但大大节省服务器程序资源,而且还减少了网页下载次数,节约了很多网络带宽。 

要使用 Rails 和 Ajax,就要创建一个空项目,并生成一个有两个方法的控制器。一个控制器控制简单的页面,另一个控制器建立一个 Ajax 响应。键入下列代码:

stale?语句会通过响应发送回一个etag与一个last_modified日期。如果下一个请求是相同的URL,那么浏览器会把这个etag和last_modified日期发送给服务器。然后stale?方法会对这两个参数进行分析,如果内容相同,则返回304,如果出现参数值不同,那么说明有新的内容,这样返回200。

二、HTTP Cache究竟有什么作用? 

rails ajax
cd ajax
script/generate controller ajax show time

必赢备用网址 3

我们通常的动态网站编程,服务器端程序根本就不去处理浏览器发送过来的If-None-Match和If-Modified-Since状态标识,只要有请求就生成网页发送给浏览器。对于一般情况来说,用户不会总是没完没了刷新一个页面,所以大家并不认为这种基于资源的缓存有什么太大的作用,但实际情况并非如此: 

第一行和第二行代码生成一个 Rails 项目,并切换到新目录。第三行代码生成一个叫做 ajax 的控制器,并查看两个动作:show 和 time。清单 1 显示了该控制器的代码:

想知道更详细的stale?方法的用法,可以查阅Rails的API文档,以及阅读Rails的手册。

1、像Google这种比较智能的网络爬虫可以有效识别资源的状态信息,如果使用这种缓存机制,可以大大减少爬虫的爬取次数。 

必赢备用网址 ,清单 1. 有两个空方法的控制器

2 确保浏览器对每次请求都接收新的数据

比方说Google每天爬JavaEye网站大概15万次左右,但实际上JavaEye每天有更新的内容不会超过1万个网页。因为很多内容更新比较快,因此Google就会反复不停的爬取,这样本身就造成了很多资源的浪费。如果我们使用HTTP Cache,那么只有当网页内容发生改变的时候,才会真正进行爬取,其他时候我们直接告诉Google的爬虫304 Not Modified就可以了。这样不但降低了服务器本身的负载和爬虫造成的网络带宽消耗,实际上也大大提高了Google爬虫的工作效率,岂不是皆大欢喜? 

class AjaxController < ApplicationController

def show
end

def time
end
end

上面的修改完成后,发生了一些有趣的事情。在很短的时间内,相同的Ajax行为被触发了许多次,而浏览器并没有向服务器发送一次请求,而都是从缓存中取得数据。虽然显然让UI变得快了很多,但这并不完全是我所想要的。我的目标是获得最佳的性能,同时还要保证屏幕上出现正确与最新的数据。

2、很多内容更新不频繁的网页,尽管用户不会频繁的刷新,但是从一个比较长的时间段来看使用HTTP Cache,仍然可以起到很大的缓存作用。 

首先在不使用 Ajax 的情况下构建两个简单视图,然后用 Ajax 将这两个视图绑定到一起。编辑 app/views/ajax 中的 show.rhtml 视图,使它和清单 2 类似:

浏览器的缓冲行为受到了三个HTTP头的flag状态的影响:cache-controll、pragma和expires

比方说一些历史讨论帖子,已经过去了几个月了,这些帖子内容很少更新。用户可能通过搜索,收藏链接,文章关联等方式时不时访问到这个页面。那么只要用户访问过一次以后,后续所有访问服务器直接发送304 Not Modified就可以了,不用真正生成页面。 

清单 2. 简单视图

想要关闭浏览器的缓存功能,你可以发送下面的代码:

3、对于历史帖子使用HTTP Cache可以避免爬虫反复的爬取。 

Ajax show


Click this link to show the current "time" %>.
def set_cache_buster      response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"      response.headers["Pragma"] = "no-cache"      response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"    end 

比方说JavaEye的论坛帖子列表页面,分页到20页后面的帖子已经很少有人直接访问了,但是从服务器日志去看,每天仍然有大量爬虫反复爬取这些分页到很后面的页面。这些页面由于用户很少去点击,所以基本上没有被应用程序的memcached缓存住,每次访问都会造成很高的资源消耗,爬虫隔一段时间就爬一次,对服务器是很大的负担。如果使用了HTTP Cache,那么只要爬虫爬过一次以后,以后无论爬虫爬多少次,都可以直接返回304 Not Modified了,极大的节省了服务器的负载。 

清单 1 和清单 2 中的代码不支持 Ajax,但我还是会仔细分析该代码。首先,看清单 1 中的控制器。两个空的控制器方法处理进来的 HTTP 请求。如果不明确地呈现一个视图(使用 render 方法),Rails 会呈现和该方法同名的视图。由于 Scriptaculous 和 Prototype 库也使用 HTTP,Rails 不需要对标准 HTTP 方法和 Ajax 方法进行区分。

然而我想要做的是这样:

三、如何在应用程序里面使用HTTP Cache 

现在将注意力转移到清单 2 中的视图。大多数代码都是简单的 HTML 代码,只有第二行的 link_to 辅助例程例外: "time" %>。

def set_must_revalidate      response.headers["Cache-Control"] = "must-revalidate"    end 

如果我们要在自己的程序里面实现HTTP Cache,是件非常简单的事情,特别是对Rails来说只需要添加一点点代码,以上面的JavaEye新闻订阅来说,只要添加一行代码: 

正如在跨越边界 之前的文章中所看到的那样,Ruby 用其表达式的值替代 之间的代码。在这个示例中,link-to 方法是一个生成简单 HTML 链接的辅助例程。可以通过执行该代码看到该链接。通过键入 script/server 启动服务器,然后将浏览器指向 。您将看到图 1 中的视图:

因为这么做可以让浏览器在每次请求时检查新加入的和被更新的数据,我在我的application_controller.rb中添加了这个方法,并且在before_filter控制器中加以调用。

Ruby代码 

图 1. 不涉及 Ajax 的简单用户界面

3在返回集合的GET方法中使用stale?(例如索引)

 必赢备用网址 4)

必赢备用网址 5

上面的stale?例子是从控制器的show方法中取出的,这是网络上非常通用的做法。如果想要使用这个方法返回一个集合,比如一个典型的控制器索引方法,那么需要想办法找出当前的集合和上次请求中的是否相同。

  1. def news  
  2.   fresh_when(:last_modified => News.last.created_at, :etag => News.last)  
  3. end  

在浏览器中,单击菜单项来查看页面源代码(在 Internet Explorer 为 View > Source ,在 Firefox 中为 View > Page Source)。您将看到清单 3 中的代码:

我的ListKungFu网站有一个类型列表List,其中包含很多ListItem。每个ListItem从属于一个List。为了在list_items_controller中找出某个ListItem集合是否有变化,我添加了名为list.updated_at的时间戳,每次写入操作时都会更新。

用最新新闻文章作为Etag,该文章最后修改时间作为资源的最后修改时间,这样就OK了。如果浏览器发送过来的标识和服务器标识一致,说明内容没有更新,直接发送304 Not Modified;如果不一致,说明内容更新,浏览器本地的缓存太古老了,那么就需要服务器真正生成页面了。 

清单 3. 由 show.rhtml 生成的视图

在ListItem.rb中:

以上只是一个最简单的例子,如果我们需要根据状态做一些更多的工作也是很容易的。比方说JavaEye博客的RSS订阅地址:  

Ajax show


Click this link to show the current time.
class ListItem < ActiveRecord::Base   belongs_to :list    after_save :update_list    after_destroy :update_list       # [...]       def update_list      self.list.updated_at = Time.now      self.list.save    end  end 

Ruby代码 

请注意清单 3 中的链接代码。该模板让 Rails 用户不必面对冗长且容易出错的 HTML 句法。(Ajax 代码也是这样运行:使用辅助方法插入 JavaScript 代码,该代码替您管理远程请求和 HTML 替换。)如果单击该链接,将看到针对 time 方法的默认视图,但我还没有实现它。为加以补救,请用清单 4 中的代码替换 app/controllers/ajax_controller.rb 中的 time 方法。为保持简单,我直接从控制器中呈现视图。稍后,我会把一切处理好并呈现视图。

这样,list_items_controller的索引方法看上去就像这样:

 必赢备用网址 6)

是一种编程技术,它使用 XML、JavaScript 和 Web 标准来创建高度交互性的 Web 页面,正如您在 Google Maps 和大量其他站点上所看到的页面那样...

def index      @list_items = @list.list_items         if stale?( :last_modified => @list.updated_at )        respond_with( @list_items )      end    end 
  1. @blogs = @blog_owner.last_blogs  
  2. @hash = @blogs.collect{|b| {b.id => b.post.modified_at.to_i + b.posts_count}}.hash  
  3. if stale?(:last_modified => (@blog_owner.last_blog.post.modified_at || @blog_owner.last_blog.post.created_at), :etag => @hash)  
  4.   render :template => "rss/blog"  
  5. end  

如果不使用updated_at字段,我也可以给List模型加上一个version字段,但这样看起来没什么必要。如果这个模型不适合你的应用,那么你需要找到另一种方法,检查集合是否被修改了,比如计算一下集合中所有对象的校验和,这也能行得通。

这个实现稍微复杂一些。我们需要判断博客订阅所有的输出文章是否有更新,所以我们用博客文章内容最后修改时间和博客的评论数量做一个hash,然后用这个hash值作为资源的Etag,那么只要这些博客文章当中任何文章内容被修改,或者有新评论,都会改变Etag值,从而通知浏览器内容有更新了。 

Rails 3.0 主要改进内容:

除了RSS订阅之外,JavaEye网站还有很多地方适合使用HTTP Cache,比方说JavaEye论坛的版面列表页面,一些经常喜欢泡论坛的用户,可能时不时会上来刷新一下版面, 看看有没有新的帖子,那么我们就不必每次用户请求的时候都去执行程序,生成页面给他。我们判断一下如果没有新帖子的话,直接告诉他304 Not Modified就可以了,在没有使用HTTP Cache之前的版面Action代码: 

  1. New Active Record query engine

Ruby代码 

示例代码:

 必赢备用网址 7)

users = User.where(:name => "david").limit(20)  users.where("age > 29")  # SELECT * FROM users  # WHERE name = "david" AND age > 29   # ORDER BY name  # LIMIT 20  users.order(:name).each { |user| puts user.name }  
  1. def board  
  2.   @topics = @forum.topics.paginate...  
  3.   @announcements = (params[:page] || 1).to_i == 1 ? Topic.find :all, :conditions => ...  
  4.   render :action => 'show'  
  5. end  
  1. New router for Action Controller

添加HTTP Cache以后,代码如下: 

示例代码:

Ruby代码 

resources :people do    resource :avatar     collection do      get :winners, :losers    end  end   # /sd34fgh/rooms  scope ':token', :token => /w{5,5}/ do    resources :rooms  end   # /descriptions  # /pl/descriptions  # /en/descriptions  scope '(:locale)', :locale => /en|pl/ do    resources :descriptions    root :to => 'projects#index'  end 

 必赢备用网址 8)

  1. New Action Mailer
  1. def board  
  2.   @topics = @forum.topics.paginate...  
  3.   if logged_in? || stale?(:last_modified => @topics[0].last_post.created_at, :etag => @topics.collect{|t| {t.id => t.posts_count}}.hash)  
  4.     @announcements = (params[:page] || 1).to_i == 1 ? Topic.find :all, :conditions...  
  5.     render :action => 'show'  
  6.   end  
  7. end  

示例代码:

对于登录用户,不使用HTTP Cache,这是因为登录用户需要实时接收站内短信通知和订阅通知,因此我们只能对匿名用户使用HTTP Cache,然后我们使用当前所有帖子id和回帖数构造hash作Etag,这样只要当前分页列表页面有任何帖子发生改变或者有了新回帖,就更新页面,否则就不必重新生成页面。 

class Notifier < ActionMailer::Base   default :from =>     "Highrise <system@#{APPLICATION_DOMAIN}>"      def new_project(digest, project, person)      @digest, @project, @person = digest, project, person       attachments['digest.pdf'] = digest.to_pdf      attachments['logo.jpg']   = File.read(project.logo_path)       mail(        :subject => "Your digest for #{project.name}",        :to => person.email_address_with_name      ) do |format|        format.text { render :text => "Something texty" }        format.html { render :text => "Something <i>texty< span>i>" }      end    end  end 

论坛帖子页面实际上也可以使用HTTP Cache,只不过Etag的hash算法稍微复杂一些,需要保证帖子的任何改动都要引起hash值的改变,示例代码如下: 

  1. Manage dependencies with Bundler

  2. 默认启用跨站点工具 XSS 保护

  3. 告别字符编码问题困扰

  4. Active Model: Validations, callbacks, etc for all models

  5. 官方的插件 API

  6. 内部重构

  7. Agnosticism with jQuery, rSpec, and Data Mapper

  8. 文档完善 

Ruby代码 

Rails 是一个用于开发数据库驱动的网络应用程序的完整框架。Rails基于MVC(模型- 视图- 控制器)设计模式。从视图中的Ajax应用,到控制器中的访问请求和反馈,到封装数据库的模型,Rails 为你提供一个纯Ruby的开发环境。发布网站时,你只需要一个数据库和一个网络服务器即可。

 必赢备用网址 9)

  1. 程序员的另类境界:Rails创始人驾保时捷参加职业赛
  2. 在Nginx上运行Ruby on Rails
  3. 解读Ruby on Rails的成功秘籍
  4. Twitter从Rails迁移到了Java
  5. 基于Ruby On Rails开发高品质Web应用
  1. def show  
  2.   @topic = Topic.find params[:id]  
  3.   user_session.update_.......  if logged_in?  
  4.   Topic.increment_counter(...) if ......  
  5.   @posts = @topic.post_by_page params[:page]  
  6.   posts_hash = @posts.collect{|p| {p.id => p.modified_at}}.hash  
  7.   topic_hash = @topic.forum_id + @topic.sys_tag_id.to_i + @topic.title.hash + @topic.status_flag.hash  
  8.   ad_hash = ...  (广告的hash算法,略)  
  9.   if logged_in? || stale?(:etag => [posts_hash, topic_hash, ad_hash])  
  10.     render  
  11.   end    
  12. end  

5月18日外电头条】而过去两周里,我一直在寻找一些能够进一步提高UI性能的方法,得到的成果主要是返回正确的HTTP状态代码,优化...

要分别根据主题贴,该分页的所有回帖和帖子页面的广告内容进行hash,计算出来一个唯一的Etag值,保证任何改动都会生成新的Etag,这样就搞定了,是不是很简单!这种帖子的缓存非常有效,可以避免Rails去render页面和下载页面,极大的减轻了服务器负载和带宽。 

再举一个需求比较特殊的例子:对于知识库搜索相关文章的推荐页面,比方说: Not Modified,那么Rails没有直接的设施给我们使用,需要我们稍微了解一些Rails的机制,自己编写,代码示例如下: 

Ruby代码 

 必赢备用网址 10)

  1. def topic  
  2.   @topic = Topic.find(params[:id])  
  3.   unless logged_in?  
  4.     if request.not_modified?(5.days.ago)  
  5.       head :not_modified  
  6.     else  
  7.       response.last_modified = Time.now  
  8.     end  
  9.   end  
  10. end  

每次用户请求,我们判断用户是否5天之内访问过该页面,如果访问过,直接返回304 Not Modified,如果没有访问过,或者上次访问已经超过了5天,那么设置最近修改时间为当前时间,然后生成页面给用户。是不是很简单? 

在给JavaEye网站所有的RSS订阅输出添加了HTTP Cache以后,通过一天的观察发现,超过一半的RSS订阅请求已经被缓存了,直接返回304 Not Modified,所以效果非常明显,由于JavaEye网站每天RSS订阅的动态请求就超过了10万次,因此添加HTTP Cache可以减轻不少服务器的负担和带宽消耗。除此之外,新闻文章页面,整个论坛频道,知识库相关推荐文章页面都可以添加HTTP Cache,粗粗计算下来,JavaEye这些页面统统使用HTTP Cache以后,网站整体性能至少可以提高10%。