Session是什麼
引言
在web開發中,session是個很是重要的概念。在許多動態網站的開發者看來,session就是一個變量,並且其表現像個黑洞,他只須要將東西在合適的時機放進這個洞裏,等須要的時候再把東西取出來。這是開發者對session最直觀的感覺,可是黑洞裏的景象或者說session內部究竟是怎麼工做的呢?當筆者向身邊的一些同事或朋友問及相關的更進一步的細節時,不少人每每要麼含糊其辭要麼主觀臆斷,所謂知其然而不知其因此然。javascript
筆者由此想到不少開發者,包括我本身,往往都是糾纏於框架甚至二次開發平臺之上,而對於其下的核心和基礎知之甚少,或者有心無力甚至絕不關心,少了逐本溯源的精神,每憶及此,無不慚愧。曾經實現過一個簡單的HttpServer,但當時因爲知識儲備和時間的問題,沒有考慮到session這塊,不過近期在工做之餘翻看了一些資料,並進行了相關實踐,小有所得,本着分享的精神,我將在本文中儘量全面地將我的對於session的理解展示給讀者,同時盡我所能地論及一些相關的知識,以期讀者在對session有所瞭解的同時也能另有所悟,正所謂授人以漁。php
Session是什麼
Session通常譯做會話,牛津詞典對其的解釋是進行某活動連續的一段時間。從不一樣的層面看待session,它有着相似但不全然相同的含義。好比,在web應用的用戶看來,他打開瀏覽器訪問一個電子商務網站,登陸、並完成購物直到關閉瀏覽器,這是一個會話。而在web應用的開發者開來,用戶登陸時我須要建立一個數據結構以存儲用戶的登陸信息,這個結構也叫作session。所以在談論session的時候要注意上下文環境。而本文談論的是一種基於HTTP協議的用以加強web應用能力的機制或者說一種方案,它不是單指某種特定的動態頁面技術,而這種能力就是保持狀態,也能夠稱做保持會話。css
爲何須要session
談及session通常是在web應用的背景之下,咱們知道web應用是基於HTTP協議的,而HTTP協議偏偏是一種無狀態協議。也就是說,用戶從A頁面跳轉到B頁面會從新發送一次HTTP請求,而服務端在返回響應的時候是沒法獲知該用戶在請求B頁面以前作了什麼的。html
對於HTTP的無狀態性的緣由,相關RFC裏並無解釋,但聯繫到HTTP的歷史以及應用場景,咱們能夠推測出一些理由:java
1. 設計HTTP最初的目的是爲了提供一種發佈和接收HTML頁面的方法。那個時候沒有動態頁面技術,只有純粹的靜態HTML頁面,所以根本不須要協議能保持狀態;jquery
2. 用戶在收到響應時,每每要花一些時間來閱讀頁面,所以若是保持客戶端和服務端之間的鏈接,那麼這個鏈接在大多數的時間裏都將是空閒的,這是一種資源的無故浪費。因此HTTP原始的設計是默認短鏈接,即客戶端和服務端完成一次請求和響應以後就斷開TCP鏈接,服務器所以沒法預知客戶端的下一個動做,它甚至都不知道這個用戶會不會再次訪問,所以讓HTTP協議來維護用戶的訪問狀態也全然沒有必要;程序員
3. 將一部分複雜性轉嫁到以HTTP協議爲基礎的技術之上可使得HTTP在協議這個層面上顯得相對簡單,而這種簡單也賦予了HTTP更強的擴展能力。事實上,session技術從本質上來說也是對HTTP協議的一種擴展。web
總而言之,HTTP的無狀態是由其歷史使命而決定的。但隨着網絡技術的蓬勃發展,人們不再知足於死板乏味的靜態HTML,他們但願web應用能動起來,因而客戶端出現了腳本和DOM技術,HTML裏增長了表單,而服務端出現了CGI等等動態技術。ajax
而正是這種web動態化的需求,給HTTP協議提出了一個難題:一個無狀態的協議怎樣才能關聯兩次連續的請求呢?也就是說無狀態的協議怎樣才能知足有狀態的需求呢?算法
此時有狀態是必然趨勢而協議的無狀態性也是木已成舟,所以咱們須要一些方案來解決這個矛盾,來保持HTTP鏈接狀態,因而出現了cookie和session。
對於此部份內容,讀者或許會有一些疑問,筆者在此先談兩點:
1. 無狀態性和長鏈接
可能有人會問,如今被普遍使用的HTTP1.1默認使用長鏈接,它仍是無狀態的嗎?
鏈接方式和有無狀態是徹底沒有關係的兩回事。由於狀態從某種意義上來說就是數據,而鏈接方式只是決定了數據的傳輸方式,而不能決定數據。長鏈接是隨着計算機性能的提升和網絡環境的改善所採起的一種合理的性能上的優化,通常狀況下,web服務器會對長鏈接的數量進行限制,以避免資源的過分消耗。
2. 無狀態性和session
Session是有狀態的,而HTTP協議是無狀態的,兩者是否矛盾呢?
Session和HTTP協議屬於不一樣層面的事物,後者屬於ISO七層模型的最高層應用層,前者不屬於後者,前者是具體的動態頁面技術來實現的,但同時它又是基於後者的。在下文中筆者會分析Servlet/Jsp技術中的session機制,這會使你對此有更深入的理解。
Cookie和Session
上面提到解決HTTP協議自身無狀態的方式有cookie和session。兩者都能記錄狀態,前者是將狀態數據保存在客戶端,後者則保存在服務端。
首先看一下cookie的工做原理,這須要有基本的HTTP協議基礎。
cookie是在RFC2109(已廢棄,被RFC2965取代)裏初次被描述的,每一個客戶端最多保持三百個cookie,每一個域名下最多20個Cookie(實際上通常瀏覽器如今都比這個多,如Firefox是50個),而每一個cookie的大小爲最多4K,不過不一樣的瀏覽器都有各自的實現。對於cookie的使用,最重要的就是要控制cookie的大小,不要放入無用的信息,也不要放入過多信息。
不管使用何種服務端技術,只要發送回的HTTP響應中包含以下形式的頭,則視爲服務器要求設置一個cookie:
Set-cookie:name=name;expires=date;path=path;domain=domain
支持cookie的瀏覽器都會對此做出反應,即建立cookie文件並保存(也多是內存cookie),用戶之後在每次發出請求時,瀏覽器都要判斷當前全部的cookie中有沒有沒失效(根據expires屬性判斷)而且匹配了path屬性的cookie信息,若是有的話,會如下面的形式加入到請求頭中發回服務端:
Cookie: name="zj"; Path="/linkage"
服務端的動態腳本會對其進行分析,並作出相應的處理,固然也能夠選擇直接忽略。
這裏牽扯到一個規範(或協議)與實現的問題,簡單來說就是規範規定了作成什麼樣子,那麼實現就必須依據規範來作,這樣才能互相兼容,可是各個實現所使用的方式卻不受約束,也能夠在實現了規範的基礎上超出規範,這就稱之爲擴展了。不管哪一種瀏覽器,只要想提供cookie的功能,那就必須依照相應的RFC規範來實現。因此這裏服務器只管發Set-cookie頭域,這也是HTTP協議無狀態性的一種體現。
須要注意的是,出於安全性的考慮,cookie能夠被瀏覽器禁用。
再看一下session的原理:
筆者沒有找到相關的RFC,由於session本就不是協議層面的事物。它的基本原理是服務端爲每個session維護一份會話信息數據,而客戶端和服務端依靠一個全局惟一的標識來訪問會話信息數據。用戶訪問web應用時,服務端程序決定什麼時候建立session,建立session能夠歸納爲三個步驟:
1. 生成全局惟一標識符(sessionid);
2. 開闢數據存儲空間。通常會在內存中建立相應的數據結構,但這種狀況下,系統一旦掉電,全部的會話數據就會丟失,若是是電子商務網站,這種事故會形成嚴重的後果。不過也能夠寫到文件裏甚至存儲在數據庫中,這樣雖然會增長I/O開銷,但session能夠實現某種程度的持久化,並且更有利於session的共享;
3. 將session的全局惟一標示符發送給客戶端。
問題的關鍵就在服務端如何發送這個session的惟一標識上。聯繫到HTTP協議,數據無非能夠放到請求行、頭域或Body裏,基於此,通常來講會有兩種經常使用的方式:cookie和URL重寫。
1. Cookie
讀者應該想到了,對,服務端只要設置Set-cookie頭就能夠將session的標識符傳送到客戶端,而客戶端此後的每一次請求都會帶上這個標識符,因爲cookie能夠設置失效時間,因此通常包含session信息的cookie會設置失效時間爲0,即瀏覽器進程有效時間。至於瀏覽器怎麼處理這個0,每一個瀏覽器都有本身的方案,但差異都不會太大(通常體如今新建瀏覽器窗口的時候);
2. URL重寫
所謂URL重寫,顧名思義就是重寫URL。試想,在返回用戶請求的頁面以前,將頁面內全部的URL後面所有以get參數的方式加上session標識符(或者加在path info部分等等),這樣用戶在收到響應以後,不管點擊哪一個連接或提交表單,都會在再帶上session的標識符,從而就實現了會話的保持。讀者可能會以爲這種作法比較麻煩,確實是這樣,可是,若是客戶端禁用了cookie的話,URL重寫將會是首選。
到這裏,讀者應該明白我前面爲何說session也算做是對HTTP的一種擴展了吧。以下兩幅圖是筆者在Firefox的Firebug插件中的截圖,能夠看到,當我第一次訪問index.jsp時,響應頭裏包含了Set-cookie頭,而請求頭中沒有。當我再次刷新頁面時,圖二顯示在響應中不在有Set-cookie頭,而在請求頭中卻有了Cookie頭。注意一下Cookie的名字:jsessionid,顧名思義,就是session的標識符,另外能夠看到兩幅圖中的jsessionid的值是相同的,緣由筆者就再也不多解釋了。另外讀者可能在一些網站上見過在最後附加了一段形如jsessionid=xxx的URL,這就是採用URL重寫來實現的session。
(圖一,首次請求index.jsp)
(圖二,再次請求index.jsp)
Cookie和session因爲實現手段不一樣,所以也各有優缺點和各自的應用場景:
1. 應用場景
Cookie的典型應用場景是Remember Me服務,即用戶的帳戶信息經過cookie的形式保存在客戶端,當用戶再次請求匹配的URL的時候,帳戶信息會被傳送到服務端,交由相應的程序完成自動登陸等功能。固然也能夠保存一些客戶端信息,好比頁面佈局以及搜索歷史等等。
Session的典型應用場景是用戶登陸某網站以後,將其登陸信息放入session,在之後的每次請求中查詢相應的登陸信息以確保該用戶合法。固然仍是有購物車等等經典場景;
2. 安全性
cookie將信息保存在客戶端,若是不進行加密的話,無疑會暴露一些隱私信息,安全性不好,通常狀況下敏感信息是通過加密後存儲在cookie中,但很容易就會被竊取。而session只會將信息存儲在服務端,若是存儲在文件或數據庫中,也有被竊取的可能,只是可能性比cookie小了太多。
Session安全性方面比較突出的是存在會話劫持的問題,這是一種安全威脅,這在下文會進行更詳細的說明。整體來說,session的安全性要高於cookie;
3. 性能
Cookie存儲在客戶端,消耗的是客戶端的I/O和內存,而session存儲在服務端,消耗的是服務端的資源。可是session對服務器形成的壓力比較集中,而cookie很好地分散了資源消耗,就這點來講,cookie是要優於session的;
4. 時效性
Cookie能夠經過設置有效期使其較長時間內存在於客戶端,而session通常只有比較短的有效期(用戶主動銷燬session或關閉瀏覽器後引起超時);
5. 其餘
Cookie的處理在開發中沒有session方便。並且cookie在客戶端是有數量和大小的限制的,而session的大小卻只以硬件爲限制,能存儲的數據無疑大了太多。
Servlet/JSP中的Session
經過上述的講解,讀者應該對session有了一個大致的認識,可是具體到某種動態頁面技術,又是怎麼實現session的呢?下面筆者將結合session的生命週期(lifecycle),從源代碼的層次來具體分析一下在servlet/jsp技術中,session是怎麼實現的。代碼部分以tomcat6.0.20做爲參考。
建立
在我問過的一些從事java web開發的人中,對於session的建立時機大都這麼回答:當我請求某個頁面的時候,session就被建立了。這句話其實很含糊,由於要建立session請求的發送是必不可少的,可是不管何種請求都會建立session嗎?錯。咱們來看一個例子。
衆所周知,jsp技術是servlet技術的反轉,在開發階段,咱們看到的是jsp頁面,但真正到運行時階段,jsp頁面是會被「翻譯」爲servlet類來執行的,例如咱們有以下jsp頁面:
<%@ page language="java" pageEncoding="ISO-8859-1" session="true"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>index.jsp</title>
</head>
<body>
This is index.jsp page.
<br>
</body>
</html>
在咱們初次請求該頁面後,在對應的work目錄能夠找到該頁面對應的java類,考慮到篇幅的緣由,在此只摘錄比較重要的一部分,有興趣的讀者能夠親自試一下:
......
response.setContentType("text/html;charset=ISO-8859-1");
pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\r\n");
out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
out.write("<html>\r\n");
......
能夠看到有一句顯式建立session的語句,它是怎麼來的呢?咱們再看一下對應的jsp頁面,在jsp的page指令中加入了session="true",意思是在該頁面啓用session,其實做爲動態技術,這個參數是默認爲true的,這很合理,在此顯示寫出來只是作一下強調。很顯然兩者有着必然的聯繫。筆者在jsp/servlet的翻譯器(org.apache.jasper.compiler)的源碼中找到了相關證據:
......
if (pageInfo.isSession())
out.printil("session = pageContext.getSession();");
out.printil("out = pageContext.getOut();");
out.printil("_jspx_out = out;");
......
上面的代碼片斷的意思是若是頁面中定義了session="true",就在生成的servlet源碼中加入session的獲取語句。這隻可以說明session建立的條件,顯然還不能說明session是如何建立的,本着逐本溯源的精神,咱們繼續往下探索。
有過servlet開發經驗的應該記得咱們是經過HttpServletRequest的getSession方法來獲取當前的session對象的:
public HttpSession getSession(boolean create);
public HttpSession getSession();
兩者的區別只是無參的getSession將create默認設置爲true而已。即:
public HttpSession getSession() {
return (getSession(true));
}
那麼這個參數到底意味着什麼呢?經過層層跟蹤,筆者終於理清了其中的脈絡,因爲函數之間的關係比較複雜,若是想更詳細地瞭解內部機制,建議去獨立閱讀tomcat相關部分的源代碼。這裏我將其中的大體流程敘述一下:
1. 用戶請求某jsp頁面,該頁面設置了session="true";
2. Servlet/jsp容器將其翻譯爲servlet,並加載、執行該servlet;
3. Servlet/jsp容器在封裝HttpServletRequest對象時根據cookie或者url中是否存在jsessionid來決定是綁定當前的session到HttpRequest仍是建立新的session對象(在請求解析階段發現並記錄jsessionid,在Request對象建立階段將session綁定);
4. 程序按需操做session,存取數據;
5. 若是是新建立的session,在結果響應時,容器會加入Set-cookie頭,以提醒瀏覽器要保持該會話(或者採用URL重寫方式將新的連接呈現給用戶)。
經過上面的敘述讀者應該瞭解了session是什麼時候建立的,這裏再從servlet這個層面總結一下:當用戶請求的servlet調用了getSession方法時,都會獲取session,至因而否建立新的session取決於當前request是否已綁定session。當客戶端在請求中加入了jsessionid標識而servlet容器根據此標識查找到了對應的session對象時,會將此session綁定到這次請求的request對象,客戶端請求中不帶jsessionid或者此jsessionid對應的session已過時失效時,session的綁定沒法完成,此時必須建立新的session。同時發送Set-cookie頭通知客戶端開始保持新的會話。
保持
理解了session的建立,就很好理解會話是如何在客戶端和服務端之間保持的了。當首次建立了session後,客戶端會在後續的請求中將session的標識符帶到服務端,服務端程序只要在須要session的時候調用getSession,服務端就能夠將對應的session綁定到當前請求,從而實現狀態的保持。固然這須要客戶端的支持,若是禁用了cookie而又不採用url重寫的話,session是沒法保持的。
若是幾回請求之間有一個servlet未調用getSession(或者乾脆請求一個靜態頁面)會不會使得會話中斷呢?這個不會發生的,由於客戶端只會將合法的cookie值傳送給服務端,至於服務端拿cookie作什麼事它是不會關心的,固然也沒法關心。Session創建以後,客戶端會一直將session的標識符傳送到服務器,不管請求的頁面是動態的、靜態的,甚至是一副圖片。
銷燬
此處談到的銷燬是指會話的廢棄,至於存儲會話信息的數據結構是回收被重用仍是直接釋放內存咱們並不關心。Session的銷燬有兩種狀況:超時和手動銷燬。
因爲HTTP協議的無狀態性,服務端沒法得知一個session對象什麼時候將再次被使用,可能用戶開啓了一個session以後再也沒有後續的訪問,並且session的保持是須要消耗必定的服務端開銷的,所以不可能一味地建立session而不去回收無用的session。這裏就引入了一個超時機制。Tomcat中的超時在web.xml裏作以下配置:
<session-config>
<session-timeout>30</session-timeout>
</session-config>
上述配置是指session在30分鐘沒有被再次使用就將其銷燬。Tomcat是怎麼計算這個30分鐘的呢?原來在getSession以後,都要調用它的access方法,修改lastAccessedTime,在銷燬session的時候就是判斷當前時間和這個lastAccessedTime的差值。
手動銷燬是指直接調用其invalidate方法,此方法其實是調用expire方法來手動將其設置爲超時。
當用戶手動請求了session的銷燬時,客戶端是沒法知道服務端的session已經被銷燬的,它依然會發送先前的session標識符到服務端。而此時若是再次請求了某個調用了getSession的servlet,服務端是沒法根據先前的session標識符找到相應的session對象的,這是又要從新建立新的session,分配新的標識符,並告知服務端更新session標識符開始保持新的會話。
Session的數據結構
在servlet/jsp中,容器是用何種數據結構來存儲session相關的變量的呢?咱們猜想一下,首先它必須被同步操做,由於在多線程環境下session是線程間共享的,而web服務器通常狀況下都是多線程的(爲了提升性能還會用到池技術);其次,這個數據結構必須容易操做,最好是傳統的鍵值對的存取方式。
那麼咱們先具體到單個session對象,它除了存儲自身的相關信息,好比id以外,tomcat的session還提供給程序員一個用以存儲其餘信息的接口(在類org.apache.catalina.session. StandardSession裏):
public void setAttribute(String name, Object value, boolean notify)
在這裏能夠追蹤到它到底使用了何種數據:
protected Map attributes = new ConcurrentHashMap();
這就很明確了,原來tomcat使用了一個ConcurrentHashMap對象存儲數據,這是java的concurrent包裏的一個類。它恰好知足了咱們所猜想的兩點需求:同步與易操做性。
那麼tomcat又是用什麼數據結構來存儲全部的session對象呢?果真仍是ConcurrentHashMap(在管理session的org.apache.catalina.session. ManagerBase類裏):
protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
具體緣由就沒必要多說了。至於其餘web服務器的具體實現也應該考慮到這兩點。
Session Hijack
Session hijack即會話劫持是一種比較嚴重的安全威脅,也是一種普遍存在的威脅,在session技術中,客戶端和服務端經過傳送session的標識符來維護會話,但這個標識符很容易就能被嗅探到,從而被其餘人利用,這屬於一種中間人攻擊。
本部分經過一個實例來講明何爲會話劫持,經過這個實例,讀者其實更能理解session的本質。
首先,我編寫了以下頁面:
<%@ page language="java" pageEncoding="ISO-8859-1" session="true"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>index.jsp</title>
</head>
<body>
This is index.jsp page.
<br>
<%
Object o = session.getAttribute("counter");
if (o == null) {
session.setAttribute("counter", 1);
} else {
Integer i = Integer.parseInt(o.toString());
session.setAttribute("counter", i + 1);
}
out.println(session.getAttribute("counter"));
%>
<a href="<%=response.encodeRedirectURL("index.jsp")%>">index</a>
</body>
</html>
頁面的功能是在session中放置一個計數器,第一次訪問該頁面,這個計數器的值初始化爲1,之後每一次訪問這個頁面計數器都加1。計數器的值會被打印到頁面。另外,爲了比較簡單地模擬,筆者禁用了客戶端(採用firefox3.0)的cookie,轉而改用URL重寫方式,由於直接複製連接要比僞造cookie方便多了。
下面,打開firefox訪問該頁面,咱們看到了計數器的值爲1:
(圖三)
而後點擊index連接來刷新計數器,注意不要刷新當前頁,由於咱們沒用採用cookie的方式,只能在url後面帶上jsessionid,而此時地址欄裏的url是沒法帶上jsessionid的。如圖四,我把計數器刷新到了20。
(圖四)
下面是最關鍵的,複製firefox地址欄裏的地址(筆者看到的是http://localhost:8080/sessio
n/index.jsp;jsessionid=1380D9F60BCE9C30C3A7CBF59454D0A5),而後打開另外一個瀏覽器,此處沒必要將其cookie禁用。這裏我打開了蘋果的safari3瀏覽器,而後將地址粘貼到其地址欄裏,回車後以下圖:
(圖五)
很奇怪吧,計數器直接到了21。這個例子筆者是在同一臺計算機上作的,不過即便換用兩臺來作,其結果也是同樣的。此時若是交替點擊兩個瀏覽器裏的index連接你會發現他們其實操縱的是同一個計數器。其實沒必要驚訝,此處safari盜用了firefox和tomcat之間的維持會話的鑰匙,即jsessionid,這屬於session hijack的一種。在tomcat看來,safari交給了它一個jsessionid,因爲HTTP協議的無狀態性,它沒法得知這個jsessionid是從firefox那裏「劫持」來的,它依然會去查找對應的session,並執行相關計算。而此時firefox也沒法得知本身的保持會話已經被「劫持」。
結語
到這裏,讀者應該對session有了更多的更深層次的瞭解,不過因爲筆者的水平以及視野有限,文中也不乏表述欠妥之處,通篇更多地描述了在servlet/jsp中的session機制,但其餘開發平臺的機制也都萬變不離其宗。只要認真思考,你會發現其實這裏林林總總之間,總有一些因果關係存在。在軟件規模日益增大的背景下,咱們更多的時候接觸到的是框架、組件,程序員的雙眼被矇蔽了,在這些框架、組件不斷產生以及版本的不斷更新中,其實有不少相對不變的東西,那就是規範、協議、模式、算法等等,真正令一我的獲得提升的仍是那些底層的支撐技術。平時多多思考的話,你就能把相似的探索轉化爲印證。作技術猶如解牛,知筋知骨方能遊刃有餘。
首先建了個攔截器,來判斷session超時。用戶登陸後會保存用戶信息在一個session裏,在session的監聽裏,session超時會銷燬保存在session裏的用戶信息,而攔截器就經過session裏是否有用戶信息來判斷session超時。(我總以爲這種方法不怎麼好。不知還有什麼更好的辦法。)
攔截器是spring-mvc的攔截器,在攔截器裏判斷是否是ajax請求:
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception
{
if (request.getSession().getAttribute("user") == null)//判斷session裏是否有用戶信息
{
if (request.getHeader("x-requested-with") != null
&& request.getHeader("x-requested-with")
.equalsIgnoreCase("XMLHttpRequest"))//若是是ajax請求響應頭會有,x-requested-with;
{
response.setHeader("sessionstatus", "timeout");//在響應頭設置session狀態
return false;
}
}
return true;
}
這樣,若是session超時,並且是ajax請求,就會在響應頭裏,sessionstatus有一個timeout;
再用一個全局的方法來處理,session超時要跳轉的頁面。
jquery 能夠用$.ajaxSetup 方法,ext也有相似的方法
//全局的ajax訪問,處理ajax清求時sesion超時
$.ajaxSetup({
contentType:"application/x-www-form-urlencoded;charset=utf-8",
complete:function(XMLHttpRequest,textStatus){
var sessionstatus=XMLHttpRequest.getResponseHeader("sessionstatus"); //經過XMLHttpRequest取得響應頭,sessionstatus,
if(sessionstatus=="timeout"){
//若是超時就處理 ,指定要跳轉的頁面
window.location.replace("${path}/common/login.do");
//下面這種方式能夠記錄以前的請求URI:
window.location.replace("${path}/common/login.do?rediret=" + enccodeURIComponent(window.location));
}
}
}
});
解決兩種狀況下的用戶訪問超時。
a)普通http請求的session超時。
b)異步http請求的session超時,使用ext後大部分的界面刷新都是異步的ajax請求。
無論是那種類型的http請求老是能夠由一個過濾器來捕捉。
分類:普通http請求的header參數中沒有x-requested-with:XMLHttpRequest頭信息,而異步的有。
其實對於常見的ajax框架,header中還有標示本身身份的header信息。
對於普通的http請求,發現session超時後直接重定向到一個超時頁面,顯示訪問超時。
對於異步http請求,發現session超時後則向請求的response中寫入特定的超時頭信息,客戶端ajax對象檢測
頭信息,發現有超時狀態標誌後調用顯示超時信息的javascript方法,提示用戶訪問超時。
服務器端session超時後在過濾器中爲response添加新的頭信息,標記該請求超時:
if(r.getHeader("x-requested-with")!=null
&& r.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")){
response.setHeader("sessionstatus","timeout");
}
使用Ext.Ajaxt對象完成異步請求的交互,Ext.Ajax是單實例對象(很是重要,全局單一Ext.Ajax實例!)。
註冊Ext.Ajax的requestcomplete事件,每一個ajax請求成功後首先響應該事件。在該事件的回調函數裏面判斷
訪問請求是否超時。使用Ext.Ajax對象的好處是,只須要引入一個包含了幾行超時處理代碼的js文件,就能夠
爲當前應用增長超時處理功能,原有代碼不須要作任何修改。
使用Ext.Ajaxt對象完成異步請求交互,假如checkUserSessionStatus是你的回調方法,每一個頁面引用:
Ext.Ajax.on('requestcomplete',checkUserSessionStatus, this);
function checkUserSessionStatus(conn,response,options){
//Ext從新封裝了response對象
if(typeof response.getResponseHeader.sessionstatus != 'undefined'){
//發現請求超時,退出處理代碼...
}
}
能夠利用的幾個特性:a)全部的ajax請求均帶有x-requested-with:XMLHttpRequest頭信息b)Ext.Ajax是單實例對象(很是重要,全局單一Ext.Ajax實例!)c)註冊Ext.Ajax的requestcomplete事件,每一個ajax請求成功後首先響應該事件(概念相似spring的aop攔截)。
jquery提供了幾個全局事件能夠用來處理session過時請求,如當ajax請求開始時會觸發ajaxStart()方法的回調函數;當ajax請求結束時,會觸發ajaxStop()方法的回調函數。這些方法都是全局的方法,所以不管建立它們的代碼位於何處,只要有ajax請求發生時,都會觸發它們。相似的事件還有:ajaxComplete(),ajaxError(),ajaxSend(),ajaxSuccess()等。
若是使某個ajax請求不受全局方法的影響,那麼能夠在使用$.ajax()方法時,將參數中的global設置爲false,jquery代碼以下:$.ajax({ url:"test.html", global:false//不觸發全局ajax事件})
對於其餘的ajax框架,解決用戶訪問請求超時這個問題的思路是相似的。
java代碼:
當用戶session過時時:
if(request.getHeader("x-requested-with")!=null
&&request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")){
PrintWriter printWriter = response.getWriter();
printWriter.print("{sessionState:0}");
printWriter.flush();
printWriter.close();
}
js部分的代碼:
/**
設置jquery的ajax全局請求參數
*/
$.ajaxSetup({
contentType:"application/x-www-form-urlencoded;charset=utf-8",
timeout:pageTimeout,
cache:false
,
complete:function(XHR,TS){
var resText=XHR.responseText;
if(resText=="{sessionState:0}"){
var nav=judgeNavigator();
if(nav.indexOf("IE:6")>-1){
window.opener=null;
window.close();
window.open(jsContextPath+'/login.jsp','');
}else{
window.open(jsContextPath+'/login.jsp','_top');
}
}
}
});
js部分
根目錄下創建/js/ext/目錄,存放全部和ext相關的js文件。/js/ext/目錄下可創建ext相關子目錄
/js/ext/adapter/ — 存放適配器jquery,prototype,yui。。。
/js/ext/experimental/ — 存放ext一些未正式推出的組件,可參考ext開發包examples例子部分。
/js/ext/plugins/ — 存放ext擴展組件,例如ext的patch文件,ext主題,擴展組建等等。
/js/ext/resources/ — 不用說了,ext開發包中的resources目錄直接拷貝。
/js/ — 目錄下能夠放一些最經常使用 的js文件。
/js/ext/ — 目錄下放置ext-all.js,ext-base.js,ext-lang-zh_CN.js,ext核心文件;
解決兩種狀況下的用戶訪問超時。
a)普通http請求的session超時。
b)異步http請求的session超時,使用ext後大部分的界面刷新都是異步的ajax請求。
無論是那種類型的http請求老是能夠由一個過濾器來捕捉。
分類:普通http請求的header參數中沒有x-requested-with:XMLHttpRequest頭信息,而異步的有。
其實對於常見的ajax框架,header中還有標示本身身份的header信息。
對於普通的http請求,發現session超時後直接重定向到一個超時頁面,顯示訪問超時。
對於異步http請求,發現session超時後則向請求的response中寫入特定的超時頭信息,客戶端ajax對象檢測
頭信息,發現有超時狀態標誌後調用顯示超時信息的javascript方法,提示用戶訪問超時。
服務器端session超時後在過濾器中爲response添加新的頭信息,標記該請求超時:
Js代碼
if(r.getHeader("x-requested-with")!=null
&& r.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")){
response.setHeader("sessionstatus","timeout");
}
if(r.getHeader("x-requested-with")!=null
&& r.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")){
response.setHeader("sessionstatus","timeout");
}
使用Ext.Ajaxt對象完成異步請求的交互,Ext.Ajax是單實例對象(很是重要,全局單一Ext.Ajax實例!)。
註冊Ext.Ajax的requestcomplete事件,每一個ajax請求成功後首先響應該事件。在該事件的回調函數裏面判斷
訪問請求是否超時。使用Ext.Ajax對象的好處是,只須要引入一個包含了幾行超時處理代碼的js文件,就能夠
爲當前應用增長超時處理功能,原有代碼不須要作任何修改。
使用Ext.Ajaxt對象完成異步請求交互,假如checkUserSessionStatus是你的回調方法,每一個頁面引用:
Js代碼
Ext.Ajax.on('requestcomplete',checkUserSessionStatus, this);
function checkUserSessionStatus(conn,response,options){
//Ext從新封裝了response對象
if(typeof response.getResponseHeader.sessionstatus != 'undefined'){
//發現請求超時,退出處理代碼...
}
}
Ext.Ajax.on('requestcomplete',checkUserSessionStatus, this);
function checkUserSessionStatus(conn,response,options){
//Ext從新封裝了response對象
if(typeof response.getResponseHeader.sessionstatus != 'undefined'){
//發現請求超時,退出處理代碼...
}
}
能夠利用的幾個特性:
a)全部的ajax請求均帶有x-requested-with:XMLHttpRequest頭信息
b)Ext.Ajax是單實例對象(很是重要,全局單一Ext.Ajax實例!)
c)註冊Ext.Ajax的requestcomplete事件,每一個ajax請求成功後首先響應該事件(概念相似spring的aop攔截)。
對於其餘的ajax框架,解決用戶訪問請求超時這個問題的思路是相似的。
在這裏推薦一個很實用的Js方法:
Js代碼
function getRootWin(){
var win = window;
while (win != win.parent){
win = win.parent;
}
return win;
}
function getRootWin(){
var win = window;
while (win != win.parent){
win = win.parent;
}
return win;
}
經過該方法,能夠在一個任意深度的iframe中調用父iframe中的方法。具體到這裏就是不管哪個iframe中的用戶訪
問請求超時,均可以經過該方法調用最外層iframe中的退出方法,這樣便爲用戶提供了一個統一的訪問超時退出的UI
呈現。
4)系統異常處理
將實際業務代碼中的各類異常封裝成IOException, ServletException異常,指定過濾器捕獲。其他處理思路同
用戶訪問超時處理。
5)添加jquery支持
使用jquery順手的且但願在Ext項目中同時使用某些jquery插件的時候,添加jquery支持。
頁面head中直接添加:
Js代碼
<link rel="stylesheet" type="text/css" href="/js/ext/resources/css/ext-all.css" />
<script type="text/javascript" src="/js/ext/adapter/jquery/jquery.js"></script>
<script type="text/javascript" src="/js/jquery.cookie.js"></script>
<script type="text/javascript" src="/js/ext/adapter/jquery/ext-jquery-adapter.js"></script>
<script type="text/javascript" src="/js/ext/ext-base.js"></script>
<script type="text/javascript" src="/js/ext/ext-all.js"></script>
<script type="text/javascript" src="/js/ext/ext-lang-zh_CN.js"></script>
<link rel="stylesheet" type="text/css" href="/js/ext/resources/css/ext-all.css" />
<script type="text/javascript" src="/js/ext/adapter/jquery/jquery.js"></script>
<script type="text/javascript" src="/js/jquery.cookie.js"></script>
<script type="text/javascript" src="/js/ext/adapter/jquery/ext-jquery-adapter.js"></script>
<script type="text/javascript" src="/js/ext/ext-base.js"></script>
<script type="text/javascript" src="/js/ext/ext-all.js"></script>
<script type="text/javascript" src="/js/ext/ext-lang-zh_CN.js"></script>
6)修改佈局
常見的佈局通常是:header,center,footer,以及一個位於頁面左側的tree menu。其實對於Ext的UI實現來講,
去掉header,footer也不錯,由於Ext的UI原本就作得挺好看再加上去掉header及footer後能夠爲center增長不
少可視區面積,一個頁面還能夠顯示更多的內容。
應該能夠支持這兩種佈局方式的切換,交給用戶選擇。
試了幾回,在border佈局初始化完畢以後再想去掉header,footer區域好像比較麻煩,ext的官方論壇上也說設
計border佈局的本意就是應付靜態呈現。
可是好像已經有javaeye上的同志實現了動態的border佈局呵呵。能夠參考一下 EXT2的動態BorderLayout組件 。
7)更換主題
去ext的官網上下載各類主題皮膚 Themes for Ext 2.0
主題皮膚文件拷貝至本地/js/ext/plugins/theme/css/,/js/ext/plugins/theme/images/ 目錄
最好將用戶選擇的主題配置保存在cookie中,這樣用戶每次登錄均可以使用相同的界面主題。
Ext主題切換:
Js代碼
if($.cookie('ext.theme') != null && $.cookie('ext.theme') != 'default'){
Ext.util.CSS.swapStyleSheet("theme","/js/ext/plugins/theme/css/"+$.cookie('ext.theme'));
}
if($.cookie('ext.theme') != null && $.cookie('ext.theme') != 'default'){
Ext.util.CSS.swapStyleSheet("theme","/js/ext/plugins/theme/css/"+$.cookie('ext.theme'));
}
8)添加自定義的toolbar圖標
直接參考javaeye上的這邊文章 共享一些Ext的圖標 便可,做者提供的圖標很好看,使用也很是簡單。
9)生成Excel文檔
最早參考的資料是extjs論壇上面的這篇文章:GridPanel directly to Excel.
做者思路不錯,就是利用javascript直接讀取GridPanel的store數據,而後生成一個描述excel文檔的xml數據,最後
再經過一個包含了該xml數據的"data" URL下載該excel。
該方法的好處是通用性比較強,生成的excel文檔也不難看,而且是不須要服務器端參與處理的一種純客戶端解決方案。
可是最大的缺點是目前IE7不支持(This needs a browser that supports data URLs. FF, Opera and IE8 will support this.)。
然後發現dojochina網站上的一個用戶整理和修改了這個生成excel文檔的實現方法。
引用
如下的幾個問題我都已經整理和修改:
一、沒有考慮到含有序號和選擇框的grid,
二、utf8轉換bug.
三、寬度的bug
四、不支持ie六、ie7和Safari
原文地址:官方Grid導出到Excel修正版 (做者給出的代碼有些小問題,須要略微進行些調整)
若是是IE瀏覽器,客戶端將以multipart/form-data方式向服務器端提交該xml數據。
原文給出了後臺由php實現時的exportexcel.php代碼。
若是後臺由java實現,exportexcel.jsp
Java代碼
<%@page import="java.util.Date"%>
<%@page import="org.apache.commons.lang.time.DateFormatUtils"%>
<%@page import="com.oreilly.servlet.multipart.*"%>
<%
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition","attachment;filename="+
(DateFormatUtils.format(new Date(),"yyyyMMddHHmmss"))+".xls" );
MultipartParser parse = new MultipartParser(request,1000000000);
Part part = null;
int maxcount = 0;
ParamPart param = null;
while(true){
part = parse.readNextPart();
if(part == null || maxcount>1000)
break;
if(part.isParam() && part.getName().equalsIgnoreCase("exportContent")){
param = (ParamPart)part;
break;
}
maxcount++;
}
if(param!=null){
response.getWriter().println(param.getStringValue());
}else{
;
}
%>
<%@page import="java.util.Date"%>
<%@page import="org.apache.commons.lang.time.DateFormatUtils"%>
<%@page import="com.oreilly.servlet.multipart.*"%>
<%
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition","attachment;filename="+
(DateFormatUtils.format(new Date(),"yyyyMMddHHmmss"))+".xls" );
MultipartParser parse = new MultipartParser(request,1000000000);
Part part = null;
int maxcount = 0;
ParamPart param = null;
while(true){
part = parse.readNextPart();
if(part == null || maxcount>1000)
break;
if(part.isParam() && part.getName().equalsIgnoreCase("exportContent")){
param = (ParamPart)part;
break;
}
maxcount++;
}
if(param!=null){
response.getWriter().println(param.getStringValue());
}else{
;
}
%>
這裏使用 com.oreilly.servlet 解析multipart/form-data類型數據。com.oreilly.servlet 很適合文件,表單混合提
交、多文件上傳的數據解析。
10)js文件管理
凡是這種基於javascript的富客戶端解決方案一大問題就是js文件太多。每一個頁面不只要導入Ext的css,js文件,
還要導入每一個頁面應用須要的一些js文件,這樣管理起來很麻煩。
原來的狀況,至少要導入:
Html代碼
<link rel="stylesheet" type="text/css" href="/js/ext/resources/css/ext-all.css" />
<script type="text/javascript" src="/js/ext/adapter/jquery/jquery.js"></script>
<script type="text/javascript" src="/js/jquery.cookie.js"></script>
<script type="text/javascript" src="/js/ext/adapter/jquery/ext-jquery-adapter.js"></script>
<script type="text/javascript" src="/js/ext/ext-base.js"></script>
<script type="text/javascript" src="/js/ext/ext-all.js"></script>
<script type="text/javascript" src="/js/ext/ext-lang-zh_CN.js"></script>
<script type="text/javascript" src="/js/extajax.js"></script>
<script type="text/javascript" src="/js/exttheme.js"></script>
<link rel="stylesheet" type="text/css" href="/js/ext/resources/css/ext-all.css" />
<script type="text/javascript" src="/js/ext/adapter/jquery/jquery.js"></script>
<script type="text/javascript" src="/js/jquery.cookie.js"></script>
<script type="text/javascript" src="/js/ext/adapter/jquery/ext-jquery-adapter.js"></script>
<script type="text/javascript" src="/js/ext/ext-base.js"></script>
<script type="text/javascript" src="/js/ext/ext-all.js"></script>
<script type="text/javascript" src="/js/ext/ext-lang-zh_CN.js"></script>
<script type="text/javascript" src="/js/extajax.js"></script>
<script type="text/javascript" src="/js/exttheme.js"></script>
推薦使用 JSLoader 管理衆多的js,css文件
1,編寫一個js文件統一管理支持全部公用css,js文件的動態導入
Js代碼
//添加jquery支持
JSLoader.loadJavaScript("/js/ext/adapter/jquery/jquery.js");
JSLoader.loadJavaScript("/js/jquery.cookie.js");
JSLoader.loadJavaScript("/js/ext/adapter/jquery/ext-jquery-adapter.js");
//Ext支持
JSLoader.loadStyleSheet("/js/ext/resources/css/ext-all.css");
JSLoader.loadJavaScript("/js/ext/ext-base.js");
JSLoader.loadJavaScript("/js/ext/ext-all.js");
JSLoader.loadJavaScript("/js/ext/ext-lang-zh_CN.js");
//加載自定義toolbar圖標css樣式
JSLoader.loadStyleSheet("/js/ext/plugins/icon/css/ext-extend.css");
//加載用戶超時,異常處理
JSLoader.loadJavaScript("/js/extajax.js");
//主題管理
JSLoader.loadJavaScript("/js/exttheme.js");
//Excel導出支持
JSLoader.loadJavaScript("/js/ext.excel.js");
//添加jquery支持
JSLoader.loadJavaScript("/js/ext/adapter/jquery/jquery.js");
JSLoader.loadJavaScript("/js/jquery.cookie.js");
JSLoader.loadJavaScript("/js/ext/adapter/jquery/ext-jquery-adapter.js");
//Ext支持
JSLoader.loadStyleSheet("/js/ext/resources/css/ext-all.css");
JSLoader.loadJavaScript("/js/ext/ext-base.js");
JSLoader.loadJavaScript("/js/ext/ext-all.js");
JSLoader.loadJavaScript("/js/ext/ext-lang-zh_CN.js");
//加載自定義toolbar圖標css樣式
JSLoader.loadStyleSheet("/js/ext/plugins/icon/css/ext-extend.css");
//加載用戶超時,異常處理
JSLoader.loadJavaScript("/js/extajax.js");
//主題管理
JSLoader.loadJavaScript("/js/exttheme.js");
//Excel導出支持
JSLoader.loadJavaScript("/js/ext.excel.js");
2,每一個頁面只須要引入:
Html代碼
<script type="text/javascript" src="/js/jsloader.js"></script>
<script type="text/javascript" src="/js/assets.js"></script>
<script type="text/javascript" src="/js/jsloader.js"></script>
<script type="text/javascript" src="/js/assets.js"></script>