程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 真實世界中的Rails,第2部分: 高級頁面緩存

真實世界中的Rails,第2部分: 高級頁面緩存

編輯:關於JAVA

使用 JavaScript 和 cookies 擴展頁面緩存

簡介:通常,與用戶相關的內容不適於使用頁面緩存,原因是針對每個用戶的內容會有細微的不同。 通過 JavaScript 和 cookies,甚至可以在顯示某些自定義用戶數據時采用頁面緩存。本文將研究 Ruby on Rails 中的高級頁面緩存。

有了頁面緩存,Rails 就可以不再介入。在某種程度上,這是件好事 ,因為您的確可以獲得優秀的性能。Rails 只需創建 HTML 頁面,將其放入目錄,之後,就可以置之於腦 後。從那時起,就由應用服務器管理這些頁面,且頁面進入應用服務器無需任何循環。從性能的角度而言 ,頁面緩存真是天賜之福。

我也鐘愛頁面緩存,Rails 使之簡單利落。只需使用一行代碼就可以 啟用緩存。如果再加入一些代碼,就能通過簡單地刪除文件操作或使用 Rails 較高層的 API 終止緩存。 這裡存在一個問題。並不是每個網站都能使用頁面緩存。如果頁面上的數據會根據訪問它的用戶而改變, 那麼就不能進行頁面緩存。而且,如果很難判斷頁面何時到期終止,就會發現頁面緩存的要求太過苛刻。

比如,幾乎在每個頁面上,ChangingThePresent.org(參閱 側欄)都有某些用戶數據是根據當前 登錄的用戶而變化的。圖 1 顯示了我們最新主頁的一部分。(我們一直在努力完善它,所以它有可能會 改變。)這個頁面呈現出的問題相對簡單。如果能判斷用戶是否已經登錄,就可以用 Flash、JavaScript 、DHTML 或任何其他基於浏覽器的代碼動態定制視圖。您會發現已登錄的用戶可以登出系統或查看其配置 文件,而已登出的用戶則可以注冊或再次登錄。

圖 1. ChangingThePresent.org 上的登錄和登出 視圖

圖 2 顯示了稍微有些高級的用戶數據視圖,我們的站點就使用了這個視圖。圖 2 中的兩個視圖有極大的不 同。為了處理頁面緩存,我必須先解決所有的差異。對於每個已登錄的用戶,我都必須替換掉頁面的登出 內容,使之顯示登錄用戶的登錄 ID 和用戶圖片。緩存這些內容會帶來另一層面的挑戰,因為每個用戶的 數據都不盡相同。

圖 2. 兩個截然不同的視圖

這種情況並非 ChangingThePresent.org 所獨有。如果需要個性化用戶體驗,那麼不可修改的 Rails 頁面緩存的使用就 會受到限制。但如果定制不多,那麼實際上還是能很容易地緩存這些頁面的。

解決這些問題的方 法很多。我更傾向於使用如下這些技巧:

在 Rails 框架的約束之內,取消頁面緩存並使用段緩 存替代它。

先加載頁面的大部分,然後使用 JavaScript 和 Ajax 加載該頁面較小的動態部分。 服務器端代碼可以檢測用戶是否登錄,然後用 Ajax 呈現合適的部分。

將某些用戶狀態(比如用 戶是否已登錄)存儲在客戶端的 cookie 中。然後,根據 cookie 的內容,使用 JavaScript 動態更改頁 面的外觀。

在這三種技巧中,我更喜歡第三種,因為第一和第二種技巧都會將 Rails 應用程序 牽扯進來。要獲得最大限度的可伸縮性,就要盡量多地使用靜態內容。在本文中,我會側重於介紹第三種 方式。請不要使用該方法存儲任何不能丟失的敏感數據,比如 ICBM 啟動代碼或信用卡號。對於我們所處 理的這些有限的數據而言,這種方法效果很好。

使用 Show and tell 還是 hide and seek?

在我剛開始試著緩存這個主頁時,我本可以簡單地用 JavaScript 替換這些鏈接。可以將這種技 巧看成是 Show-and-tell。基於我們對已登錄用戶的了解,可以使用 JavaScript 選擇性地替換或注入 Web 頁的部分內容,從而為用戶提供正確的體驗。為了進一步細分,我會進行如下操作:

創建只 具有所有用戶的公共元素的 Web 頁。

當用戶登錄時,將一些有關該用戶的數據存入 cookie,比 如說登錄信息。

然後,使用 JavaScript 依據 cookie 的內容注入 HTML 段,借此填充該頁面的 剩余部分。

對於 ChangingThePresent 主頁而言,show-and-tell 技巧有些威力過度,因為我只 有兩套鏈接要根據所登錄的用戶加以顯示。因此,我選擇了第二種技巧,我稱之為 hide-and-seek。首先 ,顯示出所有用戶的公共頁面元素,並通過每種數據可能 的隱藏版本顯示頁面的變化部分。這就是 hide 部分。然後,根據用戶的角色使用 JavaScript 在文件中找到該用戶的內容並顯示出來。這就是 seek 部 分。您可能會想,顯示所有可能數據的版本有點威力過度,實際上,選擇性地為不同的安全角色啟用多種 特性時,這種方式是十分常見的。hide-and-seek 方式非常適合 ChangingThePresent 主頁。要實現這種 方法,可以執行如下操作:

創建只具有所有用戶的公共元素的 Web 頁。

將用戶按類型分區。為每個用戶類型添加內容版 本。就我的具體情況而言,ChangingThePresent 主頁的用戶類型包括登錄用戶和登出用戶。最初,讓此 內容可見。

當用戶登錄時,將一些可區分用戶分組的數據存入 cookie,比如說用戶角色或登錄 狀態。

當用戶訪問此頁時,選擇性地顯示用戶類型的內容版本。

實現 hide and seek

對於 ChangingThePresent 主頁而言,hide-and-seek 實現起來異常簡單。在之前的圖 1 中 ,此主頁有一個部分顯示的是與用戶帳戶相關的一些鏈接。這些鏈接可以根據用戶是否登錄而變化。首要 工作是構建此頁的所有公共內容。我在本文並未給出具體做法。第二頁需要顯示出所有用戶的全部動態內 容,而不管用戶是否已經登錄:

清單 1. 在單一視圖中創建動態內容的所有版本

<div id='logged_out'>
 <%= link_to "login", :controller => 'members', :action => 

'login' %>
 <br />
 <%= link_to "register", :controller => 'members', :action => 

'signup' %>
</div>
<div id='logged_in' style="display: none;">
 <%= link_to "your profile", :controller => 'profiles', :action 

=> 'show' %>
 <%= link_to "logout" , :controller => "members", :action => 

"logout" %>
</div>

您可能已經注意到 my profile 鏈接。起初,該鏈接指向用戶特定的配置文件, 但這樣可能會妨礙我們的主頁緩存。相反,我只簡單地將此鏈接指向了無任何用戶 ID 的索引操作。然後 ,索引操作會將用戶重定向到正確的配置文件頁:

清單 2. 將用戶重定向到正確的配置文件頁

  def index
    redirect_to my_profile_url
  end

在清單 2,my_profile_url 是一個方法,該方法可以根據用戶的類型(這可能是名人 、顧問或會員)決定正確的配置文件 URL。每個用戶類型都有一個單獨的配置文件頁。這時,程序的功能 已經完成,您總共可以看到四個鏈接,logged_in 和 logged_out 各有兩個鏈接:

login

register

your profile

logout

下一步,獲取含有當前用戶類型的 cookie。對於 ChangingThePresent,我在登錄時創建了一個 cookie,其中含有當前的登錄 ID。之後, 在登出時再銷毀這個 cookie:

清單 3. 在登錄和登出時創建和銷毀 cookies

def login
 if request.post?
  self.current_user = User.authenticate(params['user_login'], params

['user_password'])
  ...
  if logged_in?
   set_cookies
   ...
  end
end
def logout
end
private
def set_cookies
 cookies[:login] = current_user.login
 cookies[:image] = find_thumb(current_user.member_image)
end
def logout
 cookies.delete :login
 cookies.delete :image
 ...
end

在清單 3 中,logged_in? 是一個私有方法,如果當前用戶已登錄則返回 true。上述的 Rails 方法會在您登錄時創建三個 cookie,並在登出時刪除它們。這裡不需要為數據費神。尚且不需用 到數據。可以這樣理解:無需調用 Rails 框架,我就可以判斷用戶是否登錄。我無需確保 cookie 到期 終止是否與站點的到期終止規定相符。在我的例子中,二者是相符的,所以我現在盡可以開始頁面緩存了 。

下一步,根據用戶的 cookie 選擇性地隱藏和顯示正確的條目。將如下的 JavaScript 代碼添 加到 public/javascripts/application.js 中:

清單 4. 支持 show and hide 登錄 div 的 JavaScript 代碼

function readCookie(name) {
  var nameEQ = name + "=";
  var ca = document.cookie.split(';');
  for(var i=0;i < ca.length;i++) {
    var c = ca[i];
    while (c.charAt(0)==' ') c = c.substring(1,c.length);
    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
  }
  return null;
}
function handle_cached_user() {
  var login_cookie = readCookie('login');
  var logged_in = document.getElementById('logged_in');
  var logged_out = document.getElementById('logged_out');
  if(login_cookie == null) {
   logged_in.style.display = 'none';
   logged_out.style.display = 'block';
  } else {
   logged_out.style.display = 'none';
   logged_in.style.display = 'block';
  }
}

第一個函數從 Javascript 中讀取 cookie 值,第二個函數處理此 DOM。可以通過使用 Prototype 庫 簡化這段代碼,但我包括了基本的 DOM 查找以便於讀者理解。最後一步是在頁面加載時調用 JavaScript 函數。我向布局中添加了如下代碼:

清單 5. 當頁面加載時調用 JavaScript 函數

  

<script type="text/javascript">
   window.onload = function() {
     handle_cached_user();
      <%= render_nifty_corners_javascript %>
      <%= yield :javascript_window_onload %>
   }
  </script>

上述 JavaScript 代碼十分簡單。在頁面加載時,將加載 handle_cached_user 函數,而它會相應地顯示或隱藏正確的內容。現在,我盡可以通過向控制器中添加 如下代碼來啟用頁面緩存:

  caches_page :index

上述代碼效果極佳。我還是需要 定期地從緩存中刪除前頁,這樣我才能使該頁期滿終止。為此,我只需簡單地定期刪除 public/index.html。hide-and-seek 方式對於有幾類用戶的頁面十分有效,但對於如圖 2 中所示的用戶 partial 效果卻不佳。對於後者,需要綜合使用 hide-and-seek 和 show-and-tell 技巧。

實現 show-and-tell

再來看看 圖 2。我將使用 hide-and-seek — 根據用戶是否已登錄 — 選擇 partial 的正確版本,然後使用 show-and-tell 技巧根據我之前在清單 3 的行 4 和行 5 中所寫 的 cookies 的內容填充頁面的動態部分。請記住,對於 show-and-tell,我特別更改了頁面的元素以符 合單個用戶的情況。

首先,完成在這兩個 partial (即登出用戶和登錄用戶)上呈現的靜態內容 。我假設用戶已經登出,所以我會通過附加 display: none 風格隱藏 logged_in div。之後,如果必要 ,我就可以用 JavaScript 顯示或隱藏它們。請注意,我使用了相同的兩個名稱:logged_in 和 logged_out,來識別每個 div,這樣便無需對我為這個主頁所編寫的 JavaScript 進行修改:

清 單 6. 呈現登錄和登出這兩個 partial

<div class="boxRight 

sideColumnColor">
  <div id='logged_in'>
    <%= render :partial => 'common/logged_in' style="display: none; 

%>
  </div>
  <div id='logged_out'>
    <%= render :partial => 'common/logged_out' %>
  </div>
</div>

接下來,完成 logged_in partial 的內容。注意,每個包含動態內容的 HTML 組 件都有一個 ID,從而我可以使用 JavaScript 找到它並隨後將其替換:

清單 7. 顯示 logged_in partial

<div id='logged_in' style="display: none;">
 <%= link_to %(<span class="mainBodyDark">Hi, </span>) +
    %(<span class="textLarge mainBodyDark"><b 

id='bold_link'>) + "my_login" +
    %(</b></span>), {:controller => 'profiles', :action => 

'show', :id => 'my_login'},
{:id => 'profile_link'} %>
 <br/>
 <div id='picture_and_link'>
   <a href="http://member/my_login" 

id='link_for_member_thumbnail'>
     <img id='member_thumbnail'
        alt="Def_member_thumbnail"
        src="/images/default/def_member_thumbnail.gif" /></a>
 </div>
 <div id="not_mine">Not my_login?</div>
 <br/>
 <%= image_button "logout", :controller => "members", :action 

=> "logout" %>

如果對 Rails 有足夠的了解,您可能會注意到其中的幾個定 制幫助程序函數。從中可以看到四處很明顯的動態內容,我需要使用 JavaScript 為每個加載的頁面替換 這些內容:三處登錄,一處會員圖像。此處的 JavaScript 代碼對 handle_cached_user 函數作了一處修 改,並且還含有一個為動態用戶處理頁面更新的方法。針對本文的具體情況,我稍微對這段代碼做了少許 簡化。可以將如下函數添加到 application.js 文件中:

清單 8. 替換用戶 partial 的元素

function handle_user_partial() {
  var login_cookie = readCookie('login');
  var image_cookie = readCookie('image');
  var profileLink = document.getElementById('profile_link');
  profileLink.href = '/member/' + login_cookie;
  document.getElementById('bold_link').firstChild.nodeValue=login_cookie;
  document.getElementById('not_mine').firstChild.nodeValue="Not " + 

login_cookie + "?";
  document.getElementById('link_for_member_thumbnail').href="/member/" + 

login_cookie;
  document.getElementById('member_thumbnail').src=image_cookie.replace(/%2

[Ff]/g,"/");
  document.getElementById('member_thumbnail').alt=login_cookie;
}

在清單 8 中,這個 JavaScript 函數首先讀取此 cookies 並獲取 DOM 樹的一部分:即到當 前的用戶配置文件的鏈接,稱為 profile_link。然後是 handle_user_partial 函數:

將登錄用戶的名稱(存儲在 login_cookie 內)替換成 my_login 以為用戶配置文件頁創建正確的 URL。

將登錄用戶名插入到 DOM 元素中,此元素使用粗體文本表示登錄用戶。

將簡單的句子 “Not login?” 插入到 DOM 元素中,這個元素包含 login partial 中的 logout 標 題。

找到包含會員圖像的 dom 元素,將一般圖像的 URL 替換成會員圖像的 URL,會員圖像保存在 image_cookie 中。

此外,還要將此圖像的 alt 標記替換成 login 名稱,以防圖像不出現。

在 DOM 中導航時,會發現有時需要直接轉到 DOM 元素,而有時又需要轉到該元素的特定子元素,比 如在處理文本的時候。我就使用了 firstChild 函數根據需要尋找 DOM 元素的第一個子元素。由於語法 更為友好,所以 Prototype 庫使處理特定的 DOM 元素較為容易一些,但這超出了本文的討論范圍。

我已經創建好了所有的 cookies,最後一步就是從 handle_cached_user 函數調用 JavaScript。請記 住,該函數在 public/javascripts/application.js中:

清單 9. 將 handle_user_partial 函數添加到 handle_cached_user

function 

handle_cached_user() {
	var login_cookie = readCookie('login');
    var logged_in = document.getElementById('logged_in');
    var logged_out = document.getElementById('logged_out');
    if(login_cookie == null) {
      logged_in.style.display = 'none';
      logged_out.style.display = 'block';
    } else {
	  handle_user_partial();
      logged_out.style.display = 'none';
      logged_in.style.display = 'block';
    }
}

請注意,else 條件中的 handle_cached_user 函數下面還有額外兩行代碼。這兩行代碼可以在使 logged_in DOM 元素可見之前進行適當的替代。剩下所需做的就是使用本篇文章和 上個月 的那篇文章中 所介紹的頁面緩存指令來緩存整個頁。

結束語

本篇文章中介紹的這種高級技巧為我們打開了許多方便之門。在 ChangingThePresent.org 上,我們 估計使用非常簡單的基於時間的清除器能夠緩存超過 75% 的頁面。通過使用稍微有些復雜的清除技巧, 我們就能緩存超過 90% 的頁面,而且還可能更多。如果您想試圖影響我們的圖像緩存計劃,那麼您只能 觸及應用服務器 1% 到 3% 的 Web 請求。

但同時,我們也應該看到不利的一面。我向此系統添加了明顯的復雜性。我必須維護更加復雜的 HTML 代碼,並確保 HTML 和 JavaScript 能夠保持同步。但好的一面是在需要獲得更好的性能時,我就能夠使 用最為簡單和有效的緩存技術。您也可以嘗試使用這種技巧 — 訪問 ChangingThePresent.org 並加載主 頁。接下來,加載每個頂端的菜單。您會發現我們會頁面緩存六個頂端菜單中的四個。創建一個帳號並重 載每一個菜單。您能猜到哪個頁面被緩存了麼?在下一篇文章中,在繼續深入真實世界中的 Rails 的同 時,我將帶您探究能增進 ActiveRecord 性能的一些技巧。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved