#169 Dynamic Page Caching
- Download:
- source codeProject Files in Zip (117 KB)
- mp4Full Size H.264 Video (17.9 MB)
- m4vSmaller H.264 Video (12.2 MB)
- webmFull Size VP8 Video (31.9 MB)
- ogvFull Size Theora Video (25.9 MB)
这次我们将要处理的是一个钢琴演奏爱好者的论坛程序。下面是它正在载入的主页。
这个网页的加载花费了很长的时间,因为它很复杂。事实上,在加载网页的同时,通过设置两秒钟的睡眠模拟延迟。我们假设这个延迟是真实存在的,但是我们想让网页尽可能快地加载完毕。我们将尽可能地优化网页的性能来缩短载入时间。
缓存页面
在rails程序中,提高网页性能的最好方法是将它缓存起来(页面缓存在98节 中已经有提及)。让我们转到页面缓存来看看我们将得到说明结果。
在开发环境下,缓存是默认关闭的,所以我们必打开文件 /config/environments/development.rb
把里面的
config.action_controller.perform_caching
设置为true
。这样我们就能为 forums controller 中的 index
action 添加缓存功能了。
class ForumsController < ApplicationController before_filter :admin_required, :except => [:index, :show] caches_page :index def index @forums = Forum.all sleep 2 end # other methods omitted. end
为了使刚才的设置生效,我们还必须重启服务器。重新加载我们的主页面。第一次它将花费几秒时间,但是当我们再次刷新是它几乎是立即显示,因为此时是从缓存中提取出来的。
缓存页面时在文件夹/public
中生成静态的HTML文件。当我们对rails程序提交request请求时,服务器就从之前的静态文件中查找对应的文件。例如,服务器将为这个页面查找/public/episodes/169-dynamic-page-caching.html
。如果服务器找到该文件,它将把该文件返回给浏览器,而rails不需要处理此次request请求。但是如果找不到文件,服务器将把请求交给rails程序处理。如果一个action请求已经打开了缓存,Rails在处理了该请求之后将把生成的静态HTML文件写到/public
目录下相应的地方,这样在下一个相同的请求到来时服务器就可以直接返回给浏览器。(另外,这也是为什么你在创建一个新的Rails程序的时候要将静态的index.html
页面删除。它的行为就像一个被缓存的文件一样,会阻止一个原始的request请求)
缓存后产生的页面没有任何动态的内容,这将产生一个问题,例如有一个复杂的页面,它的根据当前已登陆用户显示不同的内容,如果我们把整个页面都缓存起来,那么无论谁来访问,它都把当前已登陆的用户当成eifion.
<div id="user_nav"> <p>Welcome <strong>eifion</strong>! Not you? <a href="/logout">Logout</a></p> </div>
即使点击“log out”链接,我们仍然被显示成已经登陆的用户。因为我们退出后,”logout”页面重定向回主页面,而服务器直接把缓存中的静态主页面返回给我们。
这个页面看上去有太多动态内容,以至于我们不能使用页面缓存(page caching)。下面将用片段缓存(fragment caching)来缓存显示论坛列表的内容,但是即使是在这里仍然有“edit”和“destroy”这两个动态的链接,它们只对管理员用户可见。
在页面缓存中使用Javascript
我们可以把动态内容从页面移除,用Page caching把基本的页面缓存起来,然后在通过JavaScript把动态内容添加上去。
开始之前,我们把forums controller的缓存功能和睡眠功能暂时关闭。同时还要把/public/index.html
文件删除,否则我们所做的改变将产生不了效果。
rm public/index.html
在页面的最开始,我们为管理员用户提供编辑(edit)和删除(delete)论坛的链接,在页面的底部提供新建论坛(“new forum”)的链接。下面的代码由两部分组成,在if语句中的代码只对管理员可见。
<% title "Piano Forums" %> <div id="forums"> <% for forum in @forums %> <div class="forum"> <h2><%= link_to h(forum.name), forum %></h2> <p><%= h forum.description %></p> <% if admin? %> <p class="admin"> <%= link_to "Edit", edit_forum_path(forum) %> | <%= link_to "Destroy", forum, :confirm => 'Are you sure?', :method => :delete %> </p> <% end %> </div> <% end %> </div> <% if admin? %> <p class="admin"><%= link_to "New Forum", new_forum_path %></p> <% end %>
我们删除if语句然后用CSS把链接隐藏起来。
<p class="admin" style="display:none;"> <%= link_to "Edit", edit_forum_path(forum) %> | <%= link_to "Destroy", forum, :confirm => 'Are you sure?', :method => :delete %> </p> <p class="admin" style="display:none;"><%= link_to "New Forum", new_forum_path %></p>
如果我们现在重新载入页面,管理链接仍然在页面上,但已经被隐藏起来了。
现在,如果当前用户是管理员,我们就使用JavaScript来使以上管理链接重新显示出来。在视图的顶部,我们使用一个helper方法把两个JavaScript文件引入到页面的HEAD区域中。(这个helper方法是Ryan Bates nifty layout generator的一部分。我们引用的第一个JavaScript文件是jQuery library,你也可以用Prototype如果你更倾向于Prototype;第二个是UsersController
的一个action)
<% javascript 'jquery', '/users/current' %>
以上代码生成如下HTML
<script src="/javascripts/jquery.js" type="text/javascript"></script> <script src="/users/current.js" type="text/javascript"></script>
第一行代码载入jQuery库文件,我们必须先下载它然后把它放到/public/javascripts
目录中。第二行将调用users controller的show
action,现在它传递一个假的id。为了使这个show action能够响应JavaScript的请求,我们还必须先修改它。
这个controller需要有一个show
method,但是这里可以像下面这样写。
def show end
下一步我们需要一个新的视图文件,这样controller就可以响应JavaScript请求。在目录/app/views/users
中,创建一个名为show.js.erb
的文件,在里面写下如下代码
$(document).ready(function () { <% if admin? %> $('.admin').show(); <% end %> });
当文件对象模型加载完毕后,代码使用jQuery的$(document).ready()
方法来运行JavaScript代码。如果用户不是管理员,相应的代码将为空,但是,如果是管理员,页面上所有元素将显示出来。
更新状态
页面中显示用户状态的部分也要动态地生成,当用户登录或者退出时相应的提示信息也要更新。这两部分的代码都被放入当了application的layout文件中。
<%- flash.each do |name, msg| -%> <%= content_tag :div, msg, :id => "flash_#{name}" %> <%- end -%> <div id="user_status"> <% if current_user %> <p>Welcome <strong><%= current_user.username %></strong>! Not you? <%= link_to "Logout", logout_path %></p> <% else %> <%= link_to "Register", new_user_path %> <%= link_to "Log in", login_path %> <% end %> </div>
如果这部分代码放在一个partial文件中,使用JavaScript更新它们会变得更容易,所有我们把上面的代码放入/app/views/layouts/dynamic_header.html.erb
中,同时在原文件中写下下面的代码。
<%= render 'layouts/dynamic_header' %>
我们需要做的是把partial中的内容从已经缓存的静态主页文件中移除,然后用JavaScript再把它们添加回去。所以我们需要传递信息给partial让它不要再在主页中显示。
在文件/app/views/forums/index.html.erb
的顶部,我们需要增加一个变量@hide_dynamic
并且把它赋值为true
.
<% title "Piano Forums" %> <% javascript 'jquery', '/users/current' %> <% @hide_dynamic = true %> <div id="forums"> <!-- rest of page... -->
当@hide_dynamic
为true
时layout dynamic_header
就不会被引用。
<%= render :partial => 'layouts/dynamic_header' unless @hide_dynamic %>
如果现在刷新页面,我们会看到user的状态而且logout链接也消失了。我们修改我们JavaScript代码,这样就可以把它们重新添加上去
我们需要修改我们的jQuery方法,之后它用partial来显示用户的当前状态和其他新的信息。我们仅仅增加一行jQuery代码就可以达到目的。
$(document).ready(function () { $('#container').prepend('<%= escape_javascript render("layouts/dynamic_header") %>'); <% if admin? %> $('.admin').show(); <% end %> });
我们想要给id为container
的div添加这个partial作为第一个子元素,所以使用jQuery的prepend()
方法。这个方法以HTML字符串作为参数,所以我们可以传递这个partial作为参数,包裹在escape_javascript
中来保证partial中的内容不会产生JavaScript错误。
现在我们重新载入页面时用户状态重新出现,页面显示的内容和真实用户登录状态将保持一致。
Re-enabling Caching
既然我们有了JavaScript动态更新的页面部分,现在我们可以加入页面缓存功能和睡眠功能来模仿页面的复杂性。
当我们再次刷新页面,第一次同样花费几秒钟的时间,但是接下的刷新页面几乎是直接显示,因为页面是从缓存中提取出来的。与之前不同的是我们可以动态的看到我们登录或者退出的状态,而且只提供给管理员的链接是隐藏起来的,和我们期待的一样。
这个方法不会像页面缓存(full-page caching)那样高效,因为JavaScript部分仍然会到达Rails程序。但是对于复杂的文件你可以看到性能的提升,因为大部分文件是作为静态的HTML提供的。