jsp Servlet基礎入門學習(2)

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);
}
}






會話狀態概述

   HTTP協議的“無狀態”(Stateless)特點帶來了一系列的問題。特別是通過在線商店購物時,服務器不能順利地記住以前的事務就成了嚴重的問題。它使得“購物籃”之類的應用很難實現:當我們把商品加入購物籃時,服務器如何才能知道籃子裏原先有些什麼?即使服務器保存了上下文信息,我們仍舊會在電子商務應用中遇到問題。例如,當用戶從選擇商品的頁面(由普通的服務器提供)轉到輸入信用卡號和送達地址的頁面(由支持SSL的安全服務器提供),服務器如何才能記住用戶買了些什麼?

   這個問題一般有三種解決方法:

Cookie。利用HTTP Cookie來存儲有關購物會話的信息,後繼的各個連接可以查看當前會話,然後從服務器的某些地方提取有關該會話的完整信息。這是一種優秀的,也是應用最廣泛的方法。然而,即使Servlet提供了一個高級的、使用方便的Cookie接口,仍舊有一些繁瑣的細節問題需要處理:
從其他Cookie中分別出保存會話標識的Cookie。
爲Cookie設置合適的作廢時間(例如,中斷時間超過24小時的會話一般應重置)。
把會話標識和服務器端相應的信息關聯起來。(實際保存的信息可能要遠遠超過保存到Cookie的信息,而且象信用卡號等敏感信息永遠不應該用Cookie來保存。)
改寫URL。你可以把一些標識會話的數據附加到每個URL的後面,服務器能夠把該會話標識和它所保存的會話數據關聯起來。這也是一個很好的方法,而且還有當瀏覽器不支持Cookie或用戶已經禁用Cookie的情況下也有效這一優點。然而,大部分使用Cookie時所面臨的問題同樣存在,即服務器端的程序要進行許多簡單但單調冗長的處理。另外,還必須十分小心地保證每個URL後面都附加了必要的信息(包括非直接的,如通過Location給出的重定向URL)。如果用戶結束會話之後又通過書籤返回,則會話信息會丟失。
隱藏表單域。HTML表單中可以包含下面這樣的輸入域:<INPUT TYPE="HIDDEN" NAME="session" VALUE="...">。這意味着,當表單被提交時,隱藏域的名字和數據也被包含到GET或POST數據裏,我們可以利用這一機制來維持會話信息。然而,這種方法有一個很大的缺點,它要求所有頁面都是動態生成的,因爲整個問題的核心就是每個會話都要有一個唯一標識符。
   Servlet爲我們提供了一種與衆不同的方案:HttpSession API。HttpSession API是一個基於Cookie或者URL改寫機制的高級會話狀態跟蹤接口:如果瀏覽器支持Cookie,則使用Cookie;如果瀏覽器不支持Cookie或者Cookie功能被關閉,則自動使用URL改寫方法。Servlet開發者無需關心細節問題,也無需直接處理Cookie或附加到URL後面的信息,API自動爲Servlet開發者提供一個可以方便地存儲會話信息的地方。

   10.2 會話狀態跟蹤API

   在Servlet中使用會話信息是相當簡單的,主要的操作包括:查看和當前請求關聯的會話對象,必要的時候創建新的會話對象,查看與某個會話相關的信息,在會話對象中保存信息,以及會話完成或中止時釋放會話對象。

   10.2.1 查看當前請求的會話對象

   查看當前請求的會話對象通過調用HttpServletRequest的getSession方法實現。如果getSession方法返回null,你可以創建一個新的會話對象。但更經常地,我們通過指定參數使得不存在現成的會話時自動創建一個會話對象,即指定getSession的參數爲true。因此,訪問當前請求會話對象的第一個步驟通常如下所示:
HttpSession session = request.getSession(true);

   10.2.2 查看和會話有關的信息

   HttpSession對象生存在服務器上,通過Cookie或者URL這類後臺機制自動關聯到請求的發送者。會話對象提供一個內建的數據結構,在這個結構中可以保存任意數量的鍵-值對。在2.1或者更早版本的Servlet API中,查看以前保存的數據使用的是getValue("key")方法。getValue返回Object,因此你必須把它轉換成更加具體的數據類型。如果參數中指定的鍵不存在,getValue返回null。

   API 2.2版推薦用getAttribute來代替getValue,這不僅是因爲getAttribute和setAttribute的名字更加匹配(和getValue匹配的是putValue,而不是setValue),同時也因爲setAttribute允許使用一個附屬的HttpSessionBindingListener 來監視數值,而putValue則不能。

   但是,由於目前還只有很少的商業Servlet引擎支持2.2,下面的例子中我們仍舊使用getValue。這是一個很典型的例子,假定ShoppingCart是一個保存已購買商品信息的類:

HttpSession session = request.getSession(true);
ShoppingCart previousItems =
(ShoppingCart)session.getValue("previousItems");
if (previousItems != null) {
doSomethingWith(previousItems);
} else {
previousItems = new ShoppingCart(...);
doSomethingElseWith(previousItems);
}

   大多數時候我們都是根據特定的名字尋找與它關聯的值,但也可以調用getValueNames得到所有屬性的名字。getValuesNames返回的是一個String數組。API 2.2版推薦使用getAttributeNames,這不僅是因爲其名字更好,而且因爲它返回的是一個Enumeration,和其他方法(比如HttpServletRequest的getHeaders和getParameterNames)更加一致。

   雖然開發者最爲關心的往往是保存到會話對象的數據,但還有其他一些信息有時也很有用。

getID:該方法返回會話的唯一標識。有時該標識被作爲鍵-值對中的鍵使用,比如會話中只保存一個值時,或保存上一次會話信息時。
isNew:如果客戶(瀏覽器)還沒有綁定到會話則返回true,通常意味着該會話剛剛創建,而不是引用自客戶端的請求。對於早就存在的會話,返回值爲false。
getCreationTime:該方法返回建立會話的以毫秒計的時間,從1970.01.01(GMT)算起。要得到用於打印輸出的時間值,可以把該值傳遞給Date構造函數,或者GregorianCalendar的setTimeInMillis方法。
getLastAccessedTime:該方法返回客戶最後一次發送請求的以毫秒計的時間,從1970.01.01(GMT)算起。
getMaxInactiveInterval:返回以秒計的最大時間間隔,如果客戶請求之間的間隔不超過該值,Servlet引擎將保持會話有效。負數表示會話永遠不會超時。
   10.2.3 在會話對象中保存數據

   如上節所述,讀取保存在會話中的信息使用的是getValue方法(或,對於2.2版的Servlet規範,使用getAttribute)。保存數據使用putValue(或setAttribute)方法,並指定鍵和相應的值。注意putValue將替換任何已有的值。有時候這正是我們所需要的(如下例中的referringPage),但有時我們卻需要提取原來的值並擴充它(如下例previousItems)。示例代碼如下:

HttpSession session = request.getSession(true);
session.putValue("referringPage", request.getHeader("Referer"));
ShoppingCart previousItems =
(ShoppingCart)session.getValue("previousItems");
if (previousItems == null) {
previousItems = new ShoppingCart(...);
}
String itemID = request.getParameter("itemID");
previousItems.addEntry(Catalog.getEntry(itemID));

session.putValue("previousItems", previousItems);


   10.3 實例:顯示會話信息

   下面這個例子生成一個Web頁面,並在該頁面中顯示有關當前會話的信息。

package hall;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;
import java.util.*;

public class ShowSession extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(true);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String title = "Searching the Web";
String heading;
Integer accessCount = new Integer(0);;
if (session.isNew()) {
heading = "Welcome, Newcomer";
} else {
heading = "Welcome Back";
Integer oldAccessCount =
// 在Servlet API 2.2中使用getAttribute而不是getValue
(Integer)session.getValue("accessCount");
if (oldAccessCount != null) {
accessCount =
new Integer(oldAccessCount.intValue() + 1);
}
}
// 在Servlet API 2.2中使用putAttribute
session.putValue("accessCount", accessCount);

out.println(ServletUtilities.headWithTitle(title) +
"<BODY BGCOLOR=\"#FDF5E6\">\n" +
"<H1 ALIGN=\"CENTER\">" + heading + "</H1>\n" +
"<H2>Information on Your Session:</H2>\n" +
"<TABLE BORDER=1 ALIGN=CENTER>\n" +
"<TR BGCOLOR=\"#FFAD00\">\n" +
" <TH>Info Type<TH>Value\n" +
"<TR>\n" +
" <TD>ID\n" +
" <TD>" + session.getId() + "\n" +
"<TR>\n" +
" <TD>Creation Time\n" +
" <TD>" + new Date(session.getCreationTime()) + "\n" +
"<TR>\n" +
" <TD>Time of Last Access\n" +
" <TD>" + new Date(session.getLastAccessedTime()) + "\n" +
"<TR>\n" +
" <TD>Number of Previous Accesses\n" +
" <TD>" + accessCount + "\n" +
"</TABLE>\n" +
"</BODY></HTML>");
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}


 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章