cookie|js|servlet
9.1 Cookie概述
Cookie是伺服器發送給瀏覽器的體積很小的純文字資訊,使用者以後訪問同一個Web伺服器時瀏覽器會把它們原樣發送給伺服器。通過讓伺服器讀取它原先儲存到用戶端的資訊,網站能夠為瀏覽者提供一系列的方便,例如線上交易過程中標識使用者身份、安全要求不高的場合避免使用者重複輸入名字和密碼、門戶網站的首頁定製、有針對性地投放廣告,等等。
Cookie的目的就是為使用者帶來方便,為網站帶來增值。雖然有著許多誤傳,事實上Cookie並不會造成嚴重的安全威脅。Cookie永遠不會以任何方式執行,因此也不會帶來病毒或攻擊你的系統。另外,由於瀏覽器一般只允許存放300個Cookie,每個網站最多存放20個Cookie,每個Cookie的大小限制為4 KB,因此Cookie不會塞滿你的硬碟,更不會被用作“拒絕服務”攻擊手段。
9.2 Servlet的Cookie API
要把Cookie發送到用戶端,Servlet先要調用new Cookie(name,value)用合適的名字和值建立一個或多個Cookie(2.1節),通過cookie.setXXX設定各種屬性(2.2節),通過response.addCookie(cookie)把cookie加入應答頭(2.3節)。
要從用戶端讀入Cookie,Servlet應該調用request.getCookies(),getCookies()方法返回一個Cookie對象的數組。在大多數情況下,你只需要用逐一查看該數組的各個元素尋找指定名字的Cookie,然後對該Cookie調用getValue方法取得與指定名字關聯的值,這部分內容將在2.4節討論。
9.2.1 建立Cookie
調用Cookie對象的建構函式可以建立Cookie。Cookie對象的建構函式有兩個字串參數:Cookie名字和Cookie值。名字和值都不能包含空白字元以及下列字元:
[ ] ( ) = , " / ? @ : ;
9.2.2 讀取和設定Cookie屬性
把Cookie加入待發送的應答頭之前,你可以查看或設定Cookie的各種屬性。下面摘要介紹這些方法:
getComment/setComment
擷取/設定Cookie的注釋。
getDomain/setDomain
擷取/設定Cookie適用的域。一般地,Cookie只返回給與發送它的伺服器名字完全相同的伺服器。使用這裡的方法可以指示瀏覽器把Cookie返回給同一域內的其他伺服器。注意域必須以點開始(例如.sitename.com),非國家類的域(如.com,.edu,.gov)必須包含兩個點,國家類的域(如.com.cn,.edu.uk)必須包含三個點。
getMaxAge/setMaxAge
擷取/設定Cookie到期之前的時間,以秒計。如果不設定該值,則Cookie只在當前會話內有效,即在使用者關閉瀏覽器之前有效,而且這些Cookie不會儲存到磁碟上。參見下面有關LongLivedCookie的說明。
getName/setName
擷取/設定Cookie的名字。本質上,名字和值是我們始終關心的兩個部分。由於HttpServletRequest的getCookies方法返回的是一個Cookie對象的數組,因此通常要用迴圈來訪問這個數組尋找特定名字,然後用getValue檢查它的值。
getPath/setPath
擷取/設定Cookie適用的路徑。如果不指定路徑,Cookie將返回給當前頁面所在目錄及其子目錄下的所有頁面。這裡的方法可以用來設定一些更一般的條件。例如,someCookie.setPath("/"),此時伺服器上的所有頁面都可以接收到該Cookie。
getSecure/setSecure
擷取/設定一個boolean值,該值表示是否Cookie只能通過加密的串連(即SSL)發送。
getValue/setValue
擷取/設定Cookie的值。如前所述,名字和值實際上是我們始終關心的兩個方面。不過也有一些例外情況,比如把名字作為邏輯標記(也就是說,如果名字存在,則表示true)。
getVersion/setVersion
擷取/設定Cookie所遵從的協議版本。預設版本0(遵從原先的Netscape規範);版本1遵從RFC 2109 , 但尚未得到廣泛的支援。
9.2.3 在應答頭中設定Cookie
Cookie可以通過HttpServletResponse的addCookie方法加入到Set-Cookie應答頭。下面是一個例子:
Cookie userCookie = new Cookie("user", "uid1234");
response.addCookie(userCookie);
9.2.4 讀取儲存到用戶端的Cookie
要把Cookie發送到用戶端,先要建立Cookie,然後用addCookie發送一個Set-Cookie HTTP應答頭。這些內容已經在上面的2.1節介紹。從用戶端讀取Cookie時調用的是HttpServletRequest的getCookies方法。該方法返回一個與HTTP要求標頭中的內容對應的Cookie對象數組。得到這個數組之後,一般是用逐一查看其中的各個元素,調用getName檢查各個Cookie的名字,直至找到目標Cookie。然後對這個目標Cookie調用getValue,根據獲得的結果進行其他處理。
上述處理過程經常會遇到,為方便計下面我們提供一個getCookieValue方法。只要給出Cookie對象數組、Cookie名字和預設值,getCookieValue方法就會返回匹配指定名字的Cookie值,如果找不到指定Cookie,則返回預設值。
9.3 幾個Cookie工具函數
下面是幾個工具函數。這些函數雖然簡單,但是,在和Cookie打交道的時候很有用。
9.3.1 擷取指定名字的Cookie值
該函數是ServletUtilities.java的一部分。getCookieValue通過迴圈依次訪問Cookie對象數組的各個元素,尋找是否有指定名字的Cookie,如找到,則返回該Cookie的值;否則,返回參數中給出的預設值。getCookieValue能夠在一定程度上簡化Cookie值的提取。
public static String getCookieValue(Cookie[] cookies,
String cookieName,
String defaultValue) {
for(int i=0; i<cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookieName.equals(cookie.getName()))
return(cookie.getValue());
}
return(defaultValue);
}
9.3.2自動儲存的Cookie
下面是LongLivedCookie類的代碼。如果你希望Cookie能夠在瀏覽器退出的時候自動儲存下來,則可以用這個LongLivedCookie類來取代標準的Cookie類。
package hall;
import javax.servlet.http.*;
public class LongLivedCookie extends Cookie {
public static final int SECONDS_PER_YEAR = 60*60*24*365;
public LongLivedCookie(String name, String value) {
super(name, value);
setMaxAge(SECONDS_PER_YEAR);
}
}
9.4.執行個體:定製的搜尋引擎介面
下面也是一個搜尋引擎介面的例子,通過修改前面HTTP狀態碼的例子得到。在這個Servlet中,使用者介面是動態產生而不是由靜態HTML檔案提供的。Servlet除了負責讀取表單資料並把它們發送給搜尋引擎之外,還要把包含表單資料的Cookie發送給用戶端。以後客戶再次訪問同一表單時,這些Cookie的值將用來預先填充表單,使表單自動顯示最近使用過的資料。
SearchEnginesFrontEnd.java
該Servlet構造一個主要由表單構成的使用者介面。第一次顯示的時候,它和前面用靜態HTML頁面提供的介面差不多。然而,使用者選擇的值將被儲存到Cookie(本頁面將資料發送到CustomizedSearchEngines Servlet,由後者設定Cookie)。使用者以後再訪問同一頁面時,即使瀏覽器是退出之後再啟動,表單中也會自動填好上一次搜尋所填寫的內容。
注意該Servlet用到了ServletUtilities.java,其中getCookieValue前面已經介紹過,headWithTitle用於產生HTML頁面的一部分。另外,這裡也用到了前面已經說明的LongLiveCookie類,我們用它來建立作廢期限很長的Cookie。
package hall;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;
public class SearchEnginesFrontEnd extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
Cookie[] cookies = request.getCookies();
String searchString =
ServletUtilities.getCookieValue(cookies,
"searchString",
"Java Programming");
String numResults =
ServletUtilities.getCookieValue(cookies,
"numResults",
"10");
String searchEngine =
ServletUtilities.getCookieValue(cookies,
"searchEngine",
"google");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String title = "Searching the Web";
out.println(ServletUtilities.headWithTitle(title) +
"<BODY BGCOLOR=\"#FDF5E6\">\n" +
"<H1 ALIGN=\"CENTER\">Searching the Web</H1>\n" +
"\n" +
"<FORM ACTION=\"/servlet/hall.CustomizedSearchEngines\">\n" +
"<CENTER>\n" +
"Search String:\n" +
"<INPUT TYPE=\"TEXT\" NAME=\"searchString\"\n" +
" VALUE=\"" + searchString + "\"><BR>\n" +
"Results to Show Per Page:\n" +
"<INPUT TYPE=\"TEXT\" NAME=\"numResults\"\n" +
" VALUE=" + numResults + " SIZE=3><BR>\n" +
"<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
" VALUE=\"google\"" +
checked("google", searchEngine) + ">\n" +
"Google |\n" +
"<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
" VALUE=\"infoseek\"" +
checked("infoseek", searchEngine) + ">\n" +
"Infoseek |\n" +
"<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
" VALUE=\"lycos\"" +
checked("lycos", searchEngine) + ">\n" +
"Lycos |\n" +
"<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
" VALUE=\"hotbot\"" +
checked("hotbot", searchEngine) + ">\n" +
"HotBot\n" +
"<BR>\n" +
"<INPUT TYPE=\"SUBMIT\" VALUE=\"Search\">\n" +
"</CENTER>\n" +
"</FORM>\n" +
"\n" +
"</BODY>\n" +
"</HTML>\n");
}
private String checked(String name1, String name2) {
if (name1.equals(name2))
return(" CHECKED");
else
return("");
}
}
CustomizedSearchEngines.java
前面的SearchEnginesFrontEnd Servlet把資料發送到CustomizedSearchEngines Servlet。本例在許多方面與前面介紹HTTP狀態碼時的例子相似,區別在於,本例除了要構造一個針對搜尋引擎的URL並向使用者發送一個重新導向應答之外,還要發送儲存使用者資料的Cookies。
package hall;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;
public class CustomizedSearchEngines extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String searchString = request.getParameter("searchString");
Cookie searchStringCookie =
new LongLivedCookie("searchString", searchString);
response.addCookie(searchStringCookie);
searchString = URLEncoder.encode(searchString);
String numResults = request.getParameter("numResults");
Cookie numResultsCookie =
new LongLivedCookie("numResults", numResults);
response.addCookie(numResultsCookie);
String searchEngine = request.getParameter("searchEngine");
Cookie searchEngineCookie =
new LongLivedCookie("searchEngine", searchEngine);
response.addCookie(searchEngineCookie);
SearchSpec[] commonSpecs = SearchSpec.getCommonSpecs();
for(int i=0; i<commonSpecs.length; i++) {
SearchSpec searchSpec = commonSpecs[i];
if (searchSpec.getName().equals(searchEngine)) {
String url =
searchSpec.makeURL(searchString, numResults);
response.sendRedirect(url);
return;
}
}
response.sendError(response.SC_NOT_FOUND,
"No recognized search engine specified.");
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}