HTTP 代理如何正确处理 Cookie
HTTP 代理如何正確處理 Cookie
?
黃 湘平?(xphuang@cn.ibm.com), 高級軟件工程師,IBM CSDL http://www.ibm.com/developerworks/cn/java/j-cookie/?
簡介:?大多數的 Web 應用程序都要求維護某種會話狀態,如用戶購物車的內容。這種會話狀態的保持很多情況下需要借助于 Cookie 或者 Session 的幫助。本文結合在線頁面翻譯 (Machine Translation System)項目中對于 Cookie 的處理方法,探討一下如何在 HTTP 應用代理中正確處理 Cookie 的傳遞和管理問題。
?
標記本文!?
發布日期:?2005 年 11 月 10 日?
級別:?初級?
訪問情況?1778 次瀏覽?
建議:?0?(添加評論)
?
讀者定位為具有 Java 和 Web 開發經驗的開發和設計人員。
讀者可以學習到關于 Cookie 的工作原理和 Cookie 協議的細節,以及在一個 HTTP 應用代理的場景下 Cookie 的管理和處理思想,并可以直接使用文中的代碼和思路,提高工作效率。
隨著越來越多的系統移植到了 Web 上,HTTP 協議具有了比以前更廣泛的應用。不同的系統對 WEB 實現提出了不同的要求,基于 HTTP 協議的網絡應用正趨于復雜化和多元化。很多應用需要把用戶請求的頁面進行處理后再返回給用戶,比如頁面關鍵字過濾,頁面內容緩存、內容搜索、頁面翻譯等等。這些應用在實際效果上類似于一個 HTTP 應用代理:它們首先接受用戶的請求,根據用戶請求的 URL 去真正的目標服務器取回目標頁面,再根據不同應用的要求做出相應處理后返回給用戶。這樣用戶直接面對的就是這個 HTTP 應用代理,而通過它與其他頁面進行交互。Cookie 或 Session 技術的應用,解決了 HTTP 協議的一個問題 -- 無法保持客戶狀態,因此它現在被廣泛應用于各種 Web 站點中。上面提到的那些應用如果不能處理好 Cookie 和 Session 的傳遞、更新和廢除等問題,就會極大的限制它們所能處理站點的范圍,因此如何在 HTTP 應用代理中正確處理 Cookie,成為一個必須解決的問題。本文結合在頁面翻譯(Machine Translation System)項目中對于 Cookie 的處理方法,探討一下這方面的解決方案。
MTS 項目簡介及討論前提
Machine Translation System(以下簡稱 MTS)是一個在線實時頁面翻譯系統,為用戶在線提供把英文頁面翻譯成其他 9 種語言的服務。用戶通過向 MTS 系統提交一個類似下面的 URL 使用此服務,其中參數 url 指明了用戶所需要翻譯的目標地址,參數 language 指明了所需翻譯成的目標語言,www.mts.com 是假想中提供 MTS 服務的站點。
HTTP://www.mts.com/translate?url=http://www.ibm.com/&language=French
一個完整的 MTS 系統處理過程可以分解成以下幾個步驟:
- 用戶向 MTS 提交合適的 URL。
- MTS 在接到用戶的請求后,解析出用戶需要翻譯的目標地址和目標語言,根據用戶請求的目標地址,把請求轉發到目標服務器。
- MTS 接受來自目標服務器的應答,包括頁面信息和 HTTP 頭信息。
- MTS 在確定得到正確的目標頁面后,把頁面內容送入 WebSphere Translation Server 進行翻譯。
- 把翻譯后的頁面連同修改后的 HTTP 頭信息提交給用戶。
MTS 邏輯圖
?
當然,這其中涉及到很多的應用處理。比如與各種 HTTP/HTTPS 站點建立聯結、根據 HTTP 頭信息進行頁面跳轉和錯誤處理、為始終保持用戶在翻譯模式下而對目標的 HTML 頁面進行分析和修改,根據系統設置對某些 DNT(Do Not Translate)的頁面進行過濾和跳轉,當然還有對 Cookie 的處理等等。其他問題跟這篇文章關聯不大,我們重點討論在這種情況下的 Cookie 處理。Cookie 跟隨目標服務器的 HTTP 頭信息被 MTS 接收到,經過 MTS 整理之后發給客戶端瀏覽器。MTS 在接到下一次用戶對同一個站點的翻譯請求時,再把從客戶端得到的 Cookie 發送給目標服務器。
在以上的場景中,MTS 充當的作用類似于一種 HTTP 應用代理服務器,它代替用戶取得目標頁面,并在作出相應處理后再提交給用戶。當然,這種代理服務器不需要用戶修改瀏覽器的代理服務器參數或者網絡配置,而只是簡單的在瀏覽器的地址欄中輸入一個 MTS 能夠識別的 URL 即可。此篇文章也是在這樣一個應用場景的基礎上,展開對 HTTP 應用代理服務器如何處理 Cookie 的討論。
回頁首
問題的產生
在 MTS 系統中,目標服務器的 Cookie 在兩個地方會產生問題。當 MTS 接收目標服務器應答的時候,Cookie 隨著 HTTP 頭信息被 MTS 接收到的。這時候目標服務器認為 MTS 就是最終客戶,因此它賦予了 Cookie 與目標服務器相符的屬性。而如果 MTS 把這些 Cookie 原封不動的保存在 HTTP 頭信息中,傳給真正的最終用戶的話,用戶的瀏覽器會因為這些 Cookie 不合法而忽略它們。同理,當 Cookie 從瀏覽器端傳回目標服務器的時候,也會遇到相同的問題。因此有必要對 Cookie 進行一些處理,以保證用戶的瀏覽器能真正識別和利用這些 Cookie。
但是為何用戶瀏覽器無法識別從目標服務器傳過來的原始 Cookie 呢?這是因為出于安全性的考慮,Cookie 規范制定的時候對 Cookie 的產生和接受設置了一些嚴格的規范,不符合這些規范的 Cookie,瀏覽器和服務器都將予以忽略。下面我們從 Cookie 規范入手進行介紹。
回頁首
Cookie 的規范介紹
目前有以下幾種 Cookie 規范:
- Netscape cookie 草案:是最早的 cookie 規范,基于 rfc2109。盡管這個規范與 rc2109 有較大的差別,但是很多服務器都與之兼容。
- rfc2109, 是 w3c 發布的第一個官方 cookie 規范。理論上講,所有的服務器在處理 cookie( 版本 1) 時,都要遵循此規范。遺憾的是,這個規范太嚴格了,以致很多服務器不正確的實施了該規范或仍在使用 Netscape 規范。
- rfc2965 規范定義了 cookie 版本 2,并說明了 cookie 版本 1 的不足。
rfc2965 規范的使用,目前并不多。rfc2109 規范相應要嚴格得多,在實際應用上,并不是所有的瀏覽器和 Web 服務器都嚴格遵守。因此相比較而言,Netscape cookie 草案倒是一個比較簡潔和被廣泛支持的 Cookie 規范,因此我們在這里以 Netscape cookie 草案為基礎進行討論,對于其他兩種規范,我們的討論和代碼具有相同的意義。關于 Netscape cookie 草案的細節,大家可以參照 Netscape 官方站點,這里我們列舉一些和我們討論有關的內容。
根據 Netscape cookie 草案的描述,Cookie 是 Web 服務器向用戶的瀏覽器發送的一段 ASCII 碼文本。一旦收到 Cookie,瀏覽器會把 Cookie 的信息片斷以"名 / 值"對 (name-value pairs) 的形式儲存保存在本地。這以后,每當向同一個 Web 服務器請求一個新的文檔時,Web 瀏覽器都會發送之站點以前存儲在本地的 Cookie。創建 Cookie 的最初目的是想讓 Web 服務器能夠通過多個 HTTP 請求追蹤客戶。有些復雜的網絡應用需要在不同的網頁之間保持一致,它們需要這種會話狀態的保持能力。
瀏覽器與 Web 服務器通過 HTTP 協議進行通訊,而 Cookie 就是保存在 HTTP 協議的請求或者應答頭部(在 HTTP 協議中,數據包括兩部分,一部分是頭部,由一些名值對構成,用來描述要被傳輸數據的一些信息。一部分是主體 (body),是真正的數據(如 HTML 頁面等))進行傳送的。
在 HTML 文檔被發送之前,Web 服務器通過傳送 HTTP 包頭中的 Set-Cookie 消息把一個 cookie 發送到用戶的瀏覽器中。下面是一個遵循 Netscape cookie 草案的完整的 Set-Cookie 頭:
| Set-Cookie:customer=huangxp; path=/foo; domain=.ibm.com; expires= Wednesday, 19-OCT-05 23:12:40 GMT; [secure] |
Set-Cookie 的每個屬性解釋如下:
- Customer=huangxp 一個"名稱=值"對,把名稱 customer 設置為值"huangxp",這個屬性在 Cookie 中必須有。
- path=/foo 控制哪些訪問能夠觸發 cookie 的發送。如果沒有指定 path,cookie 會在所有對此站點的 HTTP 傳送時發送。如果 path=/directory,只有訪問 /directory 下面的網頁時,cookie 才被發送。在這個例子中,用戶在訪問目錄 /foo 下的內容時,瀏覽器將發送此 cookie。如果指定了 path,但是 path 與當前訪問的 url 不符,則此 cookie 將被忽略。
- domain=.ibm.com 指定 cookie 被發送到哪臺計算機上。正常情況下,cookie 只被送回最初向用戶發送 cookie 的計算機。在這個例子中,cookie 會被發送到任何在 .ibm.com 域中的主機。如果 domain 被設為空,domain 就被設置為和提供 cookie 的 Web 服務器相同。如果 domain 不為空,并且它的值又和提供 cookie 的 Web 服務器域名不符,這個 Cookie 將被忽略。
- expires= Wednesday, 19-OCT-05 23:12:40 GMT 指定 cookie 失效的時間。如果沒有指定失效時間,這個 cookie 就不會被寫入計算機的硬盤上,并且只持續到這次會話結束。
- secure 如果 secure 這個詞被作為 Set-Cookie 頭的一部分,那么 cookie 只能通過安全通道傳輸(目前即 SSL 通道)。否則,瀏覽器將忽略此 Cookie。
一旦瀏覽器接收了 cookie,這個 cookie 和對遠端 Web 服務器的連續請求將一起被瀏覽器發送。例如 前一個 cookie 被存入瀏覽器并且瀏覽器試圖請求 URL http://www.ibm.com/foo/index.html 時,下面的 HTTP 包頭就被發送到遠端的 Web 服務器。
GET /foo/index.html HTTP/1.0
Cookie:customer=huangxp
回頁首
一次典型的網絡瀏覽過程
在了解了 Cookie 協議的一些基本內容之后,讓我們看看一次典型的網絡瀏覽過程中瀏覽器如何識別和處理 Cookie:
- 瀏覽器對于 Web 服務器應答包頭中 Cookie 的操作步驟:
1. 從 Web 服務器的應答包頭中提取所有的 cookie。
2. 解析這些 cookie 的組成部分(名稱,值,路徑等等)。
3. 判定主機是否允許設置這些 cookie。允許的話,則把這些 Cookie 存儲在本地。 - 瀏覽器對 Web 服務器請求包頭中所有的 Cookie 進行篩選的步驟:
1. 根據請求的 URL 和本地存儲 cookie 的屬性,判斷那些 Cookie 能被發送給 Web 服務器。
2. 對于多個 cookie,判定發送的順序。
3. 把需要發送的 Cookie 加入到請求 HTTP 包頭中一起發送。
回頁首
由 MTS 代理的網絡瀏覽過程
以上我們了解了在一個典型的瀏覽器與 Web 服務器交互的時候,Cookie 的傳遞過程。下面我們將看到,如果在 MTS 代理網絡瀏覽的過程中,不對 Cookie 進行修改,上面的 Cookie 傳遞過程將無法實現。
1. 假設用戶希望把 http://www.ibm.com/foo/index.html 頁面翻譯成法文,應該使用如下的 url 對 MTS 發出請求
:
http://www.mts.com/translate?url=http://www.ibm.com/foo/index.html&language=French
2. MTS 接收用戶的請求,連接遠程目標服務器 http://www.ibm.com/foo/index.html。目標服務器做出應答,返回 HTTP 頭和 HTML 頁面內容。其中,典型的 HTTP 頭內容如下:
| HTTP/1.1 200 OK Date: Mon, 24 Oct 2005 06:54:41 GMT Server: IBM_HTTP_Server Cache-Control: no-cache Content-Length: 19885 Connection: close Set-Cookie:customer=huangxp; path=/foo; domain=.ibm.com; expires= Wednesday, 19-OCT-05 23:12:40 GMT Content-Type: text/html |
3. MTS 不對 Set-Cookie 后的內容作任何處理,直接把它加到用戶瀏覽器的應答頭上發送給瀏覽器。
4. 瀏覽器將從 Set-Cookie 中解析出 domain 和 path 的值,分別是 .ibm.com 和 /foo,并與請求的 url:http://www.mts.com/translate?url=http://www.ibm.com/foo/index.html&language=French 進行比較。請求 url 的 domain 是 www.mts.com,path 是 /,與 Set-Cookie 中的屬性不符,所以瀏覽器將忽略此 Cookie。
另外,在瀏覽器發送 Cookie 的時候也會遇到同樣的問題,同樣如上例,如果瀏覽器里本來已經存儲了 http://www.ibm.com/foo/ 的 Cookie,但由于用戶要通過 MTS 訪問此站點,瀏覽器經不會把已經存儲的 Cookie 上轉到 MTS 中,MTS 也就無法把之傳遞到 http://ibm.com/foo/ 上。
基于上面 Cookie 規范的介紹和例證,我們能看出,瀏覽器在接受某一個站點的 Cookie 的時候,需要檢查 Cookie 的參數 domain、path、secure,看是否與當前的站點和 URL 相符,如果不符的話,就會忽略。另一方面。瀏覽器在上傳 Cookie 的時候,也會根據當前所訪問站點的屬性,上傳相關的 Cookie,而其他的 Cookie 則不予上傳。
至此,我們討論了需要修改 Cookie 的根本原因在于 Cookie 規范的限制。下面我們討論兩種解決問題的思路。
回頁首
解決問題的兩種思路
Cookie 的存在是要解決 HTTP 協議本身先天的缺陷 - 無狀態性,它為用戶保存了一些需要的狀態信息。因此我們解決此問題的最本質的出發點,也就是找到一種途徑能為用戶保存 Cookie 所提供用戶狀態信息,實際上就是 Name/Value 對。
思路一
第一種思路就是修改目標服務器取得的 Cookie,使之符合 MTS 站點的屬性,然后作為 MTS 站點的 Cookie 存儲到用戶的瀏覽器中去。當然,這種修改必須保留原始 Cookie 的所有屬性值,當以后訪問同一個目標服務器的時候,MTS 能根據保存的屬性值還原出原始 Cookie,然后進行提交。
具體到屬性值的保存位置,沒有太多選擇的余地,實際上,domain,path,secure,expires 這幾個屬性都無法利用,只有利用 name=value 這一屬性對。我們的做法是創造一個新的 Cookie,把原始 Cookie 的 domain,path 的值與 name 值進行編碼,用分隔符附加在 Name 值的后面,符值給新的 Cookie。這樣做也同時避免了不同目標服務器如果出現同名的 Cookie,將會互相覆蓋的情況(Cookie 規范里面也規定了,客戶端以 domain,path,name 作為 Cookie 的唯一標示)。而原始 Cookie 的 secure 和 expires 值,直接符給新的 Cookie,新 Cookie 的 domain 和 path 設成缺省值,這樣,新 Cookie 就可以被瀏覽器正常接受。由于瀏覽器接受的所有 Cookie 的 domain 和 path 值都一樣,因此每次用戶對 MTS 提出請求時,瀏覽器都會把所有與 MTS 站點相關的 Cookie 上傳,因此,MTS 還需要還原原始的 Cookie,過濾掉與目標服務器不相干的 Cookie,然后上傳有用的 Cookie。
這種思路的優點在于 Cookie 存儲在客戶端,可以做到長期存儲,瀏覽器自己根據 Cookie 的 expires 值做出判斷,省掉很多開發的麻煩。缺點是轉換的過程相對較復雜。另外還有一個缺點,也是由于 Cookie 規范的限制所造成的。Cookie 規范對于一個瀏覽器同時能夠存儲的 Cookie 數量作出了規定。
- 總共 300 個 cookie
- 每個 Cookie 4 K 的存儲容量
- 每一個 domain 或者 server 20 個 cookie。
以上是瀏覽器所應達到的最小存儲數量,超出這個限制,瀏覽器應該自動按照最少最近被使用的原則刪除超出得 Cookie。由于用戶有可能通過 MTS 這一個網站翻譯大量的目標服務器,因此瀏覽器存儲在 MTS 的 domain 下的 cookie 數量就很有可能超過 20 個,這時候就會導致某些 Cookie 被刪除。一般這也不會造成太大問題,因為規范是要求瀏覽器刪除最少最近被使用的 Cookie,但我們在實際測試當中發現有些瀏覽器并不遵守這樣的規范,而是刪除最新的 Cookie,這就將導致用戶很大的不便。
思路二
第二種思路在于把原始的 Cookie 組織成 dataBean,存儲到用戶的 Session 當中去。這樣,在用戶端只需要存儲一個 SessionID 的 Cookie,而不需要存儲所有目標服務器的每一個 Cookie。另外,當接收到用戶的又一次翻譯請求時,再從 Session 當中取出所有的 dataBean,逐一進行分析,找出與用戶所請求的目標服務器相符的原始 Cookie,進行提交。
這種思路可以克服上一種思路中 Cookie 超過標準數量時的缺陷,而且不需編碼保存原始的 Cookie 屬性值,減少了程序的復雜度。缺點是需要程序員自己處理 expires。而且由于是把 Cookie 存儲在 Session 中,一旦 Session 失效,所有 Cookie 都將被刪除,所以,無法保存那些長期的 Cookie。
總之,兩種思路各有利弊,在實際應用當中要權衡考慮。下面我們針對兩種思路進行技術實現,分別對應方案一和方案二。
由于 MTS 需要與目標服務器連接,遵循 HTTP 協議讀取和返回 Cookie,但是如果用 JDK 中的 java.net.URLConnection 處理 Cookie 將非常不方便,因此我們使用 HTTPClient 來處理與目標服務器的連接。
回頁首
方案一:Cookie 存儲在瀏覽器端
用戶每發起一次新的請求,瀏覽器在檢查完本地存儲 Cookie 的有效性后,會把所有由 MTS 產生的有效 Cookie 附加在請求頭里送到 MTS。MTS 接受到客戶端的翻譯請求后,從 Request 中提取出所有的 Cookie,還原后根據目標服務器的 domain 和 path 進行過濾。產生所有與目標服務器相關的 Cookie。
| // 從 request 中獲取所有的 Cookie javax.servlet.http.Cookie[] theCookies = request.getCookies(); ArrayList cookiesList = new ArrayList(); String url = request.getParameter("url"); String domain = URLUtil.getURLHost(url); String path = URLUtil.getPath(url); if (theCookies != null) { for (int i = 0; i < theCookies.length; i++) { RE r = new RE(); // 用正則表達式把 name 項還原成 domain,path,name REDebugCompiler compiler = new REDebugCompiler(); r.setProgram(compiler.compile("//|//|")); String[] values = r.split(theCookies[i].getName()); //"9.181.116.183||/MTModule||testCookie:value1" or " || ||testCookie:value1"if (values.length == 3) { if (values[0].trim().startsWith(".")) { if (!domain.endsWith(values[0].trim())) continue; } else if (!domain.endsWith("://" + values[0].trim())) continue; if (!path.startsWith(values[1].trim())) continue; Cookie tempCookie = new Cookie(); tempCookie.setDomain( ("".equals(values[0].trim())) ? null : values[0]); tempCookie.setPath( ("".equals(values[1].trim())) ? null : values[1]); tempCookie.setName( ("".equals(values[2].trim())) ? null : values[2]); tempCookie.setSecure(theCookies[i].getSecure()); tempCookie.setValue(theCookies[i].getValue()); tempCookie.setVersion(theCookies[i].getVersion()); tempCookie.setComment(theCookies[i].getComment()); cookiesList.add(tempCookie); } } } //transferedCookie 用來存儲將被傳到目標服務器的 Cookie Cookie[] transferedCookie = new Cookie[cookiesList.size()]; cookiesList.toArray(transferedCookie); |
接下來,需要把 Cookie 送到目標服務器中。我們使用 HTTPClient 與目標服務器連接。HTTPClient 在與目標服務器連接以后,允許服務器設置 Cookie 并在需要的時候自動將 Cookie 返回服務器,也支持手工設置 Cookie 后發送到服務器端。但是,由于如何處理 cookie 有幾個規范互相沖突:Netscape Cookie 草案、RFC2109、RFC2965,而且還有很大數量的軟件商的 Cookie 實現不遵循任何規范。 為了處理這種狀況,需要把 HttpClient 設置成 Cookie 兼容模式,這樣可以最大限度的處理好各種 Cookie。下面的代碼把 Cookie 送到目標服務器。
| HttpClient client = new HttpClient(); // 從 request 得到所有需要傳輸的 cookie Cookie[] questCookie = getCookieFromRequest(request); // 設置 HTTPClient 為 Cookie 兼容模式client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY); if (questCookie.length > 0) // 把 Cookie 加到 httpclient 中client.getState().addCookies(questCookie); HttpMethod method = new GetMethod(TagerURL); // 向目標服務器發送請求int statusCode = client.executeMethod(method); method.releaseConnection(); |
MTS 把請求和 Cookie 送出后,繼續接收目標服務器的應答,讀取返回的原始 Cookie,并轉換成可以存儲在用戶瀏覽器端的 Cookie。下面的代碼將對原始 Cookie 的內容進行變換,保留 expires 和 secure 等項,把 domain 和 path 項編碼到 name 中去。
| // 從 HTTPClient 中取得所有的 Cookie Cookie[] temp = client.getState().getCookies(); if (temp != null) { javax.servlet.httpCookie theCookie = new javax.servlet.http.Cookie[temp.length]; // 逐一對 Cookie 進行處理for (int i = 0; i < temp.length; i++) { StringBuffer sb = new StringBuffer(); // 編碼成 domain||path||name sb.append( temp[i].getDomain() == null ? " " : temp[i].getDomain()); sb.append("||"); sb.append(temp[i].getPath() == null ? " " : temp[i].getPath()); sb.append("||"); sb.append(temp[i].getName() == null ? " " : temp[i].getName()); theCookie[i] = new Cookie(sb.toString(),temp[i].getValue()); // 復制其他項theCookie[i].setMaxAge(theCookie[i].getMaxAge(); theCookie[i].setSecure(temp[i].getSecure()); theCookie[i].setVersion(temp[i].getVersion()); theCookie[i].setComment(temp[i].getComment()); } } |
最后一步,把這些 Cookie 保存到 response 里,隨 HTTP 應答頭返回用戶瀏覽器。并保存在瀏覽器中。
| // 把所有轉換后的 Cookie 加入 response for (int i = 0; i < theCookie.length; i++) { response.addCookie(theCookie[i]); } |
至此,我們已經完成了接收用戶請求,轉換 Cookie,發送到目標服務器,接收目標服務器的原始 Cookie,并保存在客戶瀏覽器的整個處理過程。
回頁首
方案二:Cookie 存儲在服務器端
在此種方案中,目標服務器返回給 MTS 的 Cookie 將被組織成 dataBean,存儲在用戶的 Session 中。因此,我們首先生成一個用來存儲 Cookie 的類 CookiesBean,根據它的特性,它可以繼承 ArraryList 類。此對象將存儲用戶訪問目標服務器時接收到的所有 Cookie,并提供與新接收到的 Cookie 融合的功能,同時能夠刪除過期的 Cookie,更新同名的 Cookie。
| public class CookiesBean extends ArrayList { /** * 處理 Cookies. * @ 參數 Cookies array */ public CookiesBean(Cookie[] cook) { if (cook == null) return; //add all cookie which isn't expired. for (int i = 0; i < cook.length; i++) { if (!cook[i].isExpired()) { add(cook[i]); } } } /** * 融合參數中的 bean * @ 參數 bean * 參考 : rfc2109 4.3.3 Cookie Management */ public void RefreshBean(CookiesBean bean) { if (bean == null) return; Iterator it = bean.iterator(); // 針對 bean 中的每一個 Cookie 進行處理while (it.hasNext()) { Cookie beanCookie = (Cookie) it.next(); if (beanCookie == null) continue; ArrayList drop = new ArrayList(); Iterator thisIt = iterator(); // 取出存儲的 Cookie 進行比較和處理while (thisIt.hasNext()) { Cookie thisCookie = (Cookie) thisIt.next(); if (thisCookie == null) continue; // 比較 name,domain 和 path, 如果一樣的話,則把此 Cookie 移到 drop 中if (CommonMethods .CompString(beanCookie.getName(), thisCookie.getName()) && CommonMethods.CompString( beanCookie.getDomain(), thisCookie.getDomain()) && CommonMethods.CompString( beanCookie.getPath(), thisCookie.getPath())) { drop.add(thisCookie); continue; } // 刪除過期的 Cookie if (thisCookie.isExpired()) drop.add(thisCookie); } // 刪除所有 drop 中的 Cookie this.removeAll(drop); // 如果 beanCookie 有效,則加入到存儲區中。if (!beanCookie.isExpired()) add(beanCookie); } return; } } |
當 MTS 接受到客戶端的翻譯請求后,會從 Session 中提取出所有的 dataBean,并得到存儲的所有 Cookie。如以下代碼:
| CookiesBean dataBean = null; Cookie[] theCookies = new Cookie[0]; ArrayList cookiesList = new ArrayList(); // 獲得 Session,并獲得 dataBean HttpSession session = request.getSession(false); if (session != null) { dataBean = (CookiesBean) session.getAttribute(SESSION_NAME); } else { return theCookies; } |
MTS 在所有的存儲的 Cookie 中,檢查 Cookie 的 Domain、path 和 secure 的值,篩選出符合目標服務器的 Cookie。
| // 提取目標服務器的 domain 和 path String url = context.getURL(); String domain = URLUtil.getURLHost(url); String path = url.substring(domain.length()); String cookiedomain = null; String cookiepath = null; // 逐個比較 Cookie 的 domain 和 path // 把符合要求的 Cookie 紀錄到 cookiesList 中for (int i = 0; i < dataBean.size(); i++) { Cookie cookie = (Cookie) dataBean.get(i); if (cookie == null) continue; cookiedomain = (cookie.getDomain() == null) ? "" : cookie.getDomain(); cookiepath = (cookie.getPath() == null) ? " " : cookie.getPath(); if (!path.startsWith(cookiepath)) continue; if (cookiedomain.startsWith(".")) { if (!domain.endsWith(cookiedomain)) continue; } else if (!domain.endsWith("://" + cookiedomain)) continue; if (cookie.isExpired()) continue; if (cookie.getSecure() && url.toLowerCase().startsWith("http:")) continue; cookiesList.add(cookie); } theCookies = new Cookie[cookiesList.size()]; cookiesList.toArray(theCookies); return theCookies; |
把 Cookie 送到目標服務器的代碼與方案一基本一樣,在此忽略。
最后一步,需要把 Cookie 存儲到 Session 中。下面的代碼將從目標服務器接受 Cookie,融入到 dataBean 中,并保存到客戶的 Session 中。
| // 從目標服務器得到 Cookie 集Cookie[] cookies = client.getState().getCookies(); CookiesBean bean = new CookiesBean(cookies); CookiesBean dataBean = bean; // 取得用戶 Session HttpSession session = request.getSession(false); if (session != null) { if (session.getAttribute(SESSION_NAME) != null) { // 讀取 Session 中存取的 dataBean dataBean = (CookiesBean) session.getAttribute(SESSION_NAME); // 目標服務器端的 Cookie 融合到 Session 中的 dataBean 中dataBean.RefreshBean(bean); } // 把最終的 dataBean 存入 Session 中session.setAttribute(SESSION_NAME, dataBean); } |
至此,我們已經完成了在 Session 中保存個目標服務器所產生 Cookie 的整個處理過程。
回頁首
關于 Session 的考慮
在研究完如何管理和傳遞 Cookie 之后,我們也需要研究一下 Session 的傳遞。因為目前大部分站點都在采用 Session 機制保存用戶狀態數據,如果不能解決 Session 的傳遞問題,HTTP 應用代理服務器的適用范圍同樣會大打折扣。
首先我們了解一下 Session 的實現機制。Session 是一種服務器端的機制,服務器使用一種類似于散列表的結構來保存信息。當程序需要為某個客戶端的請求創建一個 session 的時候,服務器首先檢查這個客戶端的請求里是否已包含了一個 session 標識 - 稱為 session id,如果已包含一個 session id 則說明以前已經為此客戶端創建過 session,服務器就按照 session id 把這個 session 檢索出來使用(如果檢索不到,可能會新建一個),session id 的值應該是一個既不會重復,又不容易被找到規律以仿造的字符串。
保存這個 session id 的方式之一就是采用 Cookie。一般這個 Cookie 的名字都類似于 SESSIONID。比如 WebSphere 對于 Web 應用程序生成的 Cookie:JSESSIONID= 0001HWF4iVD94pY8Cpbx6U4CXkf:10lro0398,它的名字就是 JSESSIONID。
保存 session id 的其他方式還包括 URL 重寫和表單隱藏字段。這兩種方式都不需要代理服務器作特殊處理。因此實際上,我們解決了 Cookie 的管理和傳遞的問題之后,也就解決了 Session 的管理和傳遞。
回頁首
結束語
從上面的討論中可以看出,由于 Cookie 本身的規范限制,HTTP 應用代理所必需面對的一個問題就是如何對 Cookie 進行正確的處理。本文對此提出了兩種解決思路并列出了實現代碼。對于 MTS 項目本身,我們使用的是第二種方案。開發人員在認識好 Cookie 本身的特性之后,參照本文的思路,根據自己系統的特點,也會找出更適宜的解決方案。
參考資料
- Netscape Cookie Specification 對 Netscape Cookie 使用的特性進行了簡要的介紹。
- RFC2965:HTTP State Management Mechanism 介紹了 HTTP 狀態管理機制
- RFC2109 w3c 發布的第一個官方 cookie 規范
- RFC2616:Hypertext Transfer Protocol 超文本傳輸協議
- Ronald Tschalr 開發了 HTTPClient,將其作為 URLConnection 的替代品。
- Jakarta Regexp Apache 的開源項目,處理正則表達式的 java 包。
關于作者
黃湘平是一位 IBM CSDL 的軟件工程師,從 1997 年開始從事網絡開發設計工作。他對 Java 的網絡和 Web 開發有豐富的經驗,對底層協議也有一定的了解。現在正從事企業電子商務應用系統的開發和支持。
總結
以上是生活随笔為你收集整理的HTTP 代理如何正确处理 Cookie的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTTP代理如何正确处理Cookie(2
- 下一篇: 安全中间件与应用服务间如用session