JSP及語法概要 (山無言)

JSP及語法概要  (山無言)

有一點別搞混了~!到現在還有不明白的就是javascript與java編程語言是不一樣的!

 


JavaServer Pages(JSP)使得我們能夠分離頁面的靜態HTML和動態部分。HTML可以用任何通常使用的Web製作工具編寫,編寫方式也和原來的一樣;動態部分的代碼放入特殊標記之內,大部分以“<%”開始,以“%>”結束。例如,下面是一個JSP頁面的片斷,如果我們用http://host/OrderConfirmation.jsp?title=Core+Web+Programming這個URL打開該頁面,則結果顯示“Thanks for ordering Core Web Programming”。 
  Thanks for ordering 
  <I><%= request.getParameter("title") %></I> 
   
   
   
     JSP頁面文件通常以.jsp爲擴展名,而且可以安裝到任何能夠存放普通Web頁面的地方。雖然從代碼編寫來看,JSP頁面更象普通Web頁面而不象Servlet,但實際上,JSP最終會被轉換成正規的Servlet,靜態HTML直接輸出到和Servlet service方法關聯的輸出流。 
   
     JSP到Servlet的轉換過程一般在出現第一次頁面請求時進行。因此,如果你希望第一個用戶不會由於JSP頁面轉換成Servlet而等待太長的時間,希望確保Servlet已經正確地編譯並裝載,你可以在安裝JSP頁面之後自己請求一下這個頁面。 
   
     另外也請注意,許多Web服務器允許定義別名,所以一個看起來指向HTML文件的URL實際上可能指向Servlet或JSP頁面。 
   
     除了普通HTML代碼之外,嵌入JSP頁面的其他成分主要有如下三種:腳本元素(Scripting Element),指令(Directive),動作(Action)。腳本元素用來嵌入Java代碼,這些Java代碼將成爲轉換得到的Servlet的一部分;JSP指令用來從整體上控制Servlet的結構;動作用來引入現有的組件或者控制JSP引擎的行爲。爲了簡化腳本元素,JSP定義了一組可以直接使用的變量(預定義變量),比如前面代碼片斷中的request就是其中一例。 
   
     注意本文以JSP 1.0規範爲基礎。和0.92版相比,新版本的JSP作了許多重大的改動。雖然這些改動只會使JSP變得更好,但應注意1.0的JSP頁面幾乎和早期的JSP引擎完全不兼容。 
   
     11.2 JSP語法概要表 JSP元素 語法 說明 備註 
  JSP表達式 <%= expression %> 計算表達式並輸出結果。 等價的XML表達是: 
  <jsp:expression> 
  expression 
  </jsp:expression> 
   
  可以使用的預定義變量包括:request,response,out,session,application,config,pageContext。這些預定義變量也可以在JSP Scriptlet中使用。 
   
  JSP Scriptlet <% code %> 插入到service方法的代碼。 等價的XML表達是: 
  <jsp:scriptlet> 
  code 
  </jsp:scriptlet> 
   
  JSP聲明 <%! code %> 代碼被插入到Servlet類(在service方法之外)。 等價的XML表達是: 
  <jsp:declaration> 
  code 
  </jsp:declaration> 
   
  page指令 <%@ page att="val" %> 作用於Servlet引擎的全局性指令。 等價的XML表達是 
  <jsp:directive.page att="val"\>。 
   
  合法的屬性如下表,其中粗體表示默認值: 
   
  import="package.class" 
  contentType="MIME-Type" 
  isThreadSafe="true|false" 
  session="true|false" 
  buffer="size kb|none" 
  autoflush="true|false" 
  extends="package.class" 
  info="message" 
  errorPage="url" 
  isErrorPage="true|false" 
  language="java" 
   
  include指令 <%@ include file="url" %> 當JSP轉換成Servlet時,應當包含本地系統上的指定文件。 等價的XML表達是: 
   
  <jsp:directive.include 
  file="url"\>. 
   
  其中URL必須是相對URL。 
   
  利用jsp:include動作可以在請求的時候(而不是JSP轉換成Servlet時)引入文件。 
   
  JSP註釋 <%-- comment --%> 註釋;JSP轉換成Servlet時被忽略。 如果要把註釋嵌入結果HTML文檔,使用普通的HTML註釋標記<-- comment -->。 
  jsp:include動作 <jsp:include 
  page="relative URL" 
  flush="true"/> 當Servlet被請求時,引入指定的文件。 如果你希望在頁面轉換的時候包含某個文件,使用JSP include指令。 
  注意:在某些服務器上,被包含文件必須是HTML文件或JSP文件,具體由服務器決定(通常根據文件擴展名判斷)。 
   
  jsp:useBean動作 <jsp:useBean att=val*/> 或者 
  <jsp:useBean att=val*> 
  ... 
  </jsp:useBean> 尋找或實例化一個Java Bean。 可能的屬性包括: 
  id="name" 
  scope="page|request 
  |session|application" 
  class="package.class" 
  type="package.class" 
  beanName="package.class" 
   
  jsp:setProperty動作 <jsp:setProperty att=val*/> 設置Bean的屬性。既可以設置一個確定的值,也可以指定屬性值來自請求參數。 合法的屬性包括: 
  name="beanName" 
  property="propertyName|*" 
  param="parameterName" 
  value="val" 
   
  jsp:getProperty動作 <jsp:getProperty 
  name="propertyName" 
  value="val"/> 提取並輸出Bean的屬性。   
  jsp:forward動作 <jsp:forward 
  page="relative URL"/> 把請求轉到另外一個頁面。   
  jsp:plugin動作 <jsp:plugin 
  attribute="value"*> 
  ... 
  </jsp:plugin> 根據瀏覽器類型生成OBJECT或者EMBED標記,以便通過Java Plugin運行Java Applet。   
   
   
     11.3 關於模板文本(靜態HTML) 
   
     許多時候,JSP頁面的很大一部分都由靜態HTML構成,這些靜態HTML也稱爲“模板文本”。模板文本和普通HTML幾乎完全相同,它們都遵從相同的語法規則,而且模板文本也是被Servlet直接發送到客戶端。此外,模板文本也可以用任何現有的頁面製作工具來編寫。 
   
     唯一的例外在於,如果要輸出“<%”,則模板文本中應該寫成“<\%”。 

10.1 會話狀態概述 
   
     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); 
   } 
  } 
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); 
   } 
  } 
8.1 HTTP應答頭概述 
   
     Web服務器的HTTP應答一般由以下幾項構成:一個狀態行,一個或多個應答頭,一個空行,內容文檔。設置HTTP應答頭往往和設置狀態行中的狀態代碼結合起來。例如,有好幾個表示“文檔位置已經改變”的狀態代碼都伴隨着一個Location頭,而401(Unauthorized)狀態代碼則必須伴隨一個WWW-Authenticate頭。 
   
     然而,即使在沒有設置特殊含義的狀態代碼時,指定應答頭也是很有用的。應答頭可以用來完成:設置Cookie,指定修改日期,指示瀏覽器按照指定的間隔刷新頁面,聲明文檔的長度以便利用持久HTTP連接,……等等許多其他任務。 
   
     設置應答頭最常用的方法是HttpServletResponse的setHeader,該方法有兩個參數,分別表示應答頭的名字和值。和設置狀態代碼相似,設置應答頭應該在發送任何文檔內容之前進行。 
   
     setDateHeader方法和setIntHeadr方法專門用來設置包含日期和整數值的應答頭,前者避免了把Java時間轉換爲GMT時間字符串的麻煩,後者則避免了把整數轉換爲字符串的麻煩。 
   
     HttpServletResponse還提供了許多設置常見應答頭的簡便方法,如下所示: 
   
  setContentType:設置Content-Type頭。大多數Servlet都要用到這個方法。 
  setContentLength:設置Content-Length頭。對於支持持久HTTP連接的瀏覽器來說,這個函數是很有用的。 
  addCookie:設置一個Cookie(Servlet API中沒有setCookie方法,因爲應答往往包含多個Set-Cookie頭)。 
  另外,如上節介紹,sendRedirect方法設置狀態代碼302時也會設置Location頭。 
     8.2 常見應答頭及其含義 
   
     有關HTTP頭詳細和完整的說明,請參見http://www.w3.org/Protocols/ 規範。 
   
  應答頭 說明 
  Allow 服務器支持哪些請求方法(如GET、POST等)。 
  Content-Encoding 文檔的編碼(Encode)方法。只有在解碼之後纔可以得到Content-Type頭指定的內容類型。利用gzip壓縮文檔能夠顯著地減少HTML文檔的下載時間。Java的GZIPOutputStream可以很方便地進行gzip壓縮,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此,Servlet應該通過查看Accept-Encoding頭(即request.getHeader("Accept-Encoding"))檢查瀏覽器是否支持gzip,爲支持gzip的瀏覽器返回經gzip壓縮的HTML頁面,爲其他瀏覽器返回普通頁面。 
  Content-Length 表示內容長度。只有當瀏覽器使用持久HTTP連接時才需要這個數據。如果你想要利用持久連接的優勢,可以把輸出文檔寫入ByteArrayOutputStram,完成後查看其大小,然後把該值放入Content-Length頭,最後通過byteArrayStream.writeTo(response.getOutputStream()發送內容。 
  Content-Type 表示後面的文檔屬於什麼MIME類型。Servlet默認爲text/plain,但通常需要顯式地指定爲text/html。由於經常要設置Content-Type,因此HttpServletResponse提供了一個專用的方法setContentTyep。 
  Date 當前的GMT時間。你可以用setDateHeader來設置這個頭以避免轉換時間格式的麻煩。 
  Expires 應該在什麼時候認爲文檔已經過期,從而不再緩存它? 
  Last-Modified 文檔的最後改動時間。客戶可以通過If-Modified-Since請求頭提供一個日期,該請求將被視爲一個條件GET,只有改動時間遲於指定時間的文檔纔會返回,否則返回一個304(Not Modified)狀態。Last-Modified也可用setDateHeader方法來設置。 
  Location 表示客戶應當到哪裏去提取文檔。Location通常不是直接設置的,而是通過HttpServletResponse的sendRedirect方法,該方法同時設置狀態代碼爲302。 
  Refresh 表示瀏覽器應該在多少時間之後刷新文檔,以秒計。除了刷新當前文檔之外,你還可以通過setHeader("Refresh", "5; URL=http://host/path")讓瀏覽器讀取指定的頁面。 
  注意這種功能通常是通過設置HTML頁面HEAD區的<META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path">實現,這是因爲,自動刷新或重定向對於那些不能使用CGI或Servlet的HTML編寫者十分重要。但是,對於Servlet來說,直接設置Refresh頭更加方便。 
   
  注意Refresh的意義是“N秒之後刷新本頁面或訪問指定頁面”,而不是“每隔N秒刷新本頁面或訪問指定頁面”。因此,連續刷新要求每次都發送一個Refresh頭,而發送204狀態代碼則可以阻止瀏覽器繼續刷新,不管是使用Refresh頭還是<META HTTP-EQUIV="Refresh" ...>。 
   
  注意Refresh頭不屬於HTTP 1.1正式規範的一部分,而是一個擴展,但Netscape和IE都支持它。 
   
  Server 服務器名字。Servlet一般不設置這個值,而是由Web服務器自己設置。 
  Set-Cookie 設置和頁面關聯的Cookie。Servlet不應使用response.setHeader("Set-Cookie", ...),而是應使用HttpServletResponse提供的專用方法addCookie。參見下文有關Cookie設置的討論。 
  WWW-Authenticate 客戶應該在Authorization頭中提供什麼類型的授權信息?在包含401(Unauthorized)狀態行的應答中這個頭是必需的。例如,response.setHeader("WWW-Authenticate", "BASIC realm=\"executives\"")。 
  注意Servlet一般不進行這方面的處理,而是讓Web服務器的專門機制來控制受密碼保護頁面的訪問(例如.htaccess)。 
   
   
   
     8.3 實例:內容改變時自動刷新頁面 
   
     下面這個Servlet用來計算大素數。因爲計算非常大的數字(例如500位)可能要花不少時間,所以Servlet將立即返回已經找到的結果,同時在後臺繼續計算。後臺計算使用一個優先級較低的線程以避免過多地影響Web服務器的性能。如果計算還沒有完成,Servlet通過發送Refresh頭指示瀏覽器在幾秒之後繼續請求新的內容。 
   
     注意,本例除了說明HTTP應答頭的用處之外,還顯示了Servlet的另外兩個很有價值的功能。首先,它表明Servlet能夠處理多個併發的連接,每個都有自己的線程。Servlet維護了一份已有素數計算請求的Vector表,通過查找素數個數(素數列表的長度)和數字個數(每個素數的長度)將當前請求和已有請求相匹配,把所有這些請求同步到這個列表上。第二,本例證明,在Servlet中維持請求之間的狀態信息是非常容易的。維持狀態信息在傳統的CGI編程中是一件很麻煩的事情。由於維持了狀態信息,瀏覽器能夠在刷新頁面時訪問到正在進行的計算過程,同時也使得Servlet能夠保存一個有關最近請求結果的列表,當一個新的請求指定了和最近請求相同的參數時可以立即返回結果。 
   
     PrimeNumbers.java 
   
     注意,該Servlet要用到前面給出的ServletUtilities.java。另外還要用到:PrimeList.java,用於在後臺線程中創建一個素數的Vector;Primes.java,用於隨機生成BigInteger類型的大數字,檢查它們是否是素數。(此處略去PrimeList.java和Primes.java的代碼。) 
  package hall; 
   
  import java.io.*; 
  import javax.servlet.*; 
  import javax.servlet.http.*; 
  import java.util.*; 
   
  public class PrimeNumbers extends HttpServlet { 
   private static Vector primeListVector = new Vector(); 
   private static int maxPrimeLists = 30; 
   
   public void doGet(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   int numPrimes = ServletUtilities.getIntParameter(request, "numPrimes", 50); 
   int numDigits = ServletUtilities.getIntParameter(request, "numDigits", 120); 
   PrimeList primeList = findPrimeList(primeListVector, numPrimes, numDigits); 
   if (primeList == null) { 
   primeList = new PrimeList(numPrimes, numDigits, true); 
   synchronized(primeListVector) { 
   if (primeListVector.size() >= maxPrimeLists) 
   primeListVector.removeElementAt(0); 
   primeListVector.addElement(primeList); 
   } 
   } 
   Vector currentPrimes = primeList.getPrimes(); 
   int numCurrentPrimes = currentPrimes.size(); 
   int numPrimesRemaining = (numPrimes - numCurrentPrimes); 
   boolean isLastResult = (numPrimesRemaining == 0); 
   if (!isLastResult) { 
   response.setHeader("Refresh", "5"); 
   } 
   response.setContentType("text/html"); 
   PrintWriter out = response.getWriter(); 
   String title = "Some " + numDigits + "-Digit Prime Numbers"; 
   out.println(ServletUtilities.headWithTitle(title) + 
   "<BODY BGCOLOR=\"#FDF5E6\">\n" + 
   "<H2 ALIGN=CENTER>" + title + "</H2>\n" + 
   "<H3>Primes found with " + numDigits + 
   " or more digits: " + numCurrentPrimes + ".</H3>"); 
   if (isLastResult) 
   out.println("<B>Done searching.</B>"); 
   else 
   out.println("<B>Still looking for " + numPrimesRemaining + 
   " more<BLINK>...</BLINK></B>"); 
   out.println("<OL>"); 
   for(int i=0; i<numCurrentPrimes; i++) { 
   out.println(" <LI>" + currentPrimes.elementAt(i)); 
   } 
   out.println("</OL>"); 
   out.println("</BODY></HTML>"); 
   } 
   
   public void doPost(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   doGet(request, response); 
   } 
   
   // 檢查是否存在同類型請求(已經完成,或者正在計算)。 
   // 如存在,則返回現有結果而不是啓動新的後臺線程。 
   private PrimeList findPrimeList(Vector primeListVector, 
   int numPrimes, 
   int numDigits) { 
   synchronized(primeListVector) { 
   for(int i=0; i<primeListVector.size(); i++) { 
   PrimeList primes = (PrimeList)primeListVector.elementAt(i); 
   if ((numPrimes == primes.numPrimes()) && 
   (numDigits == primes.numDigits())) 
   return(primes); 
   } 
   return(null); 
   } 
   } 
  } 
   
   
   
   
     PrimeNumbers.html 
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
  <HTML> 
  <HEAD> 
   <TITLE>大素數計算</TITLE> 
  </HEAD> 
  <CENTER> 
  <BODY BGCOLOR="#FDF5E6"> 
  <FORM ACTION="/servlet/hall.PrimeNumbers"> 
   <B>要計算幾個素數:</B> 
   <INPUT TYPE="TEXT" NAME="numPrimes" value=25 SIZE=4><BR> 
   <B>每個素數的位數:</B> 
   <INPUT TYPE="TEXT" NAME="numDigits" value=150 SIZE=3><BR> 
   <INPUT TYPE="SUBMIT" value="開始計算"> 
  </FORM> 
  </CENTER> 
  </BODY> 
  </HTML> 
狀態代碼概述 
   
     Web服務器響應瀏覽器或其他客戶程序的請求時,其應答一般由以下幾個部分組成:一個狀態行,幾個應答頭,一個空行,內容文檔。下面是一個最簡單的應答: 
  HTTP/1.1 200 OK 
  Content-Type: text/plain 
   
  Hello World 
   
   
   
   
     狀態行包含HTTP版本、狀態代碼、與狀態代碼對應的簡短說明信息。在大多數情況下,除了Content-Type之外的所有應答頭都是可選的。但Content-Type是必需的,它描述的是後面文檔的MIME類型。雖然大多數應答都包含一個文檔,但也有一些不包含,例如對HEAD請求的應答永遠不會附帶文檔。有許多狀態代碼實際上用來標識一次失敗的請求,這些應答也不包含文檔(或只包含一個簡短的錯誤信息說明)。 
   
     Servlet可以利用狀態代碼來實現許多功能。例如,可以把用戶重定向到另一個網站;可以指示出後面的文檔是圖片、PDF文件或HTML文件;可以告訴用戶必須提供密碼才能訪問文檔;等等。這一部分我們將具體討論各種狀態代碼的含義以及利用這些代碼可以做些什麼。 
   
     7.2 設置狀態代碼 
   
     如前所述,HTTP應答狀態行包含HTTP版本、狀態代碼和對應的狀態信息。由於狀態信息直接和狀態代碼相關,而HTTP版本又由服務器確定,因此需要Servlet設置的只有一個狀態代碼。 
   
     Servlet設置狀態代碼一般使用HttpServletResponse的setStatus方法。setStatus方法的參數是一個整數(即狀態代碼),不過爲了使得代碼具有更好的可讀性,可以用HttpServletResponse中定義的常量來避免直接使用整數。這些常量根據HTTP 1.1中的標準狀態信息命名,所有的名字都加上了SC前綴(Status Code的縮寫)並大寫,同時把空格轉換成了下劃線。也就是說,與狀態代碼404對應的狀態信息是“Not Found”,則HttpServletResponse中的對應常量名字爲SC_NOT_FOUND。但有兩個例外:和狀態代碼302對應的常量根據HTTP 1.0命名,而307沒有對應的常量。 
   
     設置狀態代碼並非總是意味着不要再返回文檔。例如,雖然大多數服務器返回404應答時會輸出簡單的“File Not Found”信息,但Servlet也可以定製這個應答。不過,定製應答時應當在通過PrintWriter發送任何內容之前先調用response.setStatus。 
   
     雖然設置狀態代碼一般使用的是response.setStauts(int)方法,但爲了簡單起見,HttpServletResponse爲兩種常見的情形提供了專用方法:sendError方法生成一個404應答,同時生成一個簡短的HTML錯誤信息文檔;sendRedirect方法生成一個302應答,同時在Location頭中指示新文檔的URL。 
   
     7.3 HTTP 1.1狀態代碼及其含義 
   
     下表顯示了常見的HTTP 1.1狀態代碼以及它們對應的狀態信息和含義。 
   
     應當謹慎地使用那些只有HTTP 1.1支持的狀態代碼,因爲許多瀏覽器還只能夠支持HTTP 1.0。如果你使用了HTTP 1.1特有的狀態代碼,最好能夠檢查一下請求的HTTP版本號(通過HttpServletRequest的getProtocol方法)。 狀態代碼 狀態信息 含義 
  100 Continue 初始的請求已經接受,客戶應當繼續發送請求的其餘部分。(HTTP 1.1新) 
  101 Switching Protocols 服務器將遵從客戶的請求轉換到另外一種協議(HTTP 1.1新) 
  200 OK 一切正常,對GET和POST請求的應答文檔跟在後面。如果不用setStatus設置狀態代碼,Servlet默認使用202狀態代碼。 
  201 Created 服務器已經創建了文檔,Location頭給出了它的URL。 
  202 Accepted 已經接受請求,但處理尚未完成。 
  203 Non-Authoritative Information 文檔已經正常地返回,但一些應答頭可能不正確,因爲使用的是文檔的拷貝(HTTP 1.1新)。 
  204 No Content 沒有新文檔,瀏覽器應該繼續顯示原來的文檔。如果用戶定期地刷新頁面,而Servlet可以確定用戶文檔足夠新,這個狀態代碼是很有用的。 
  205 Reset Content 沒有新的內容,但瀏覽器應該重置它所顯示的內容。用來強制瀏覽器清除表單輸入內容(HTTP 1.1新)。 
  206 Partial Content 客戶發送了一個帶有Range頭的GET請求,服務器完成了它(HTTP 1.1新)。 
  300 Multiple Choices 客戶請求的文檔可以在多個位置找到,這些位置已經在返回的文檔內列出。如果服務器要提出優先選擇,則應該在Location應答頭指明。 
  301 Moved Permanently 客戶請求的文檔在其他地方,新的URL在Location頭中給出,瀏覽器應該自動地訪問新的URL。 
  302 Found 類似於301,但新的URL應該被視爲臨時性的替代,而不是永久性的。注意,在HTTP1.0中對應的狀態信息是“Moved Temporatily”,而HttpServletResponse中相應的常量是SC_MOVED_TEMPORARILY,而不是SC_FOUND。 
  出現該狀態代碼時,瀏覽器能夠自動訪問新的URL,因此它是一個很有用的狀態代碼。爲此,Servlet提供了一個專用的方法,即sendRedirect。使用response.sendRedirect(url)比使用response.setStatus(response.SC_MOVED_TEMPORARILY)和response.setHeader("Location",url)更好。這是因爲: 
   
  首先,代碼更加簡潔。 
  第二,使用sendRedirect,Servlet會自動構造一個包含新鏈接的頁面(用於那些不能自動重定向的老式瀏覽器)。 
  最後,sendRedirect能夠處理相對URL,自動把它們轉換成絕對URL。 
  注意這個狀態代碼有時候可以和301替換使用。例如,如果瀏覽器錯誤地請求http://host/~user(缺少了後面的斜槓),有的服務器返回301,有的則返回302。 
   
  嚴格地說,我們只能假定只有當原來的請求是GET時瀏覽器纔會自動重定向。請參見307。 
   
  303 See Other 類似於301/302,不同之處在於,如果原來的請求是POST,Location頭指定的重定向目標文檔應該通過GET提取(HTTP 1.1新)。 
  304 Not Modified 客戶端有緩衝的文檔併發出了一個條件性的請求(一般是提供If-Modified-Since頭表示客戶只想比指定日期更新的文檔)。服務器告訴客戶,原來緩衝的文檔還可以繼續使用。 
  305 Use Proxy 客戶請求的文檔應該通過Location頭所指明的代理服務器提取(HTTP 1.1新)。 
  307 Temporary Redirect 和302(Found)相同。許多瀏覽器會錯誤地響應302應答進行重定向,即使原來的請求是POST,即使它實際上只能在POST請求的應答是303時才能重定向。由於這個原因,HTTP 1.1新增了307,以便更加清除地區分幾個狀態代碼:當出現303應答時,瀏覽器可以跟隨重定向的GET和POST請求;如果是307應答,則瀏覽器只能跟隨對GET請求的重定向。 
  注意,HttpServletResponse中沒有爲該狀態代碼提供相應的常量。(HTTP 1.1新) 
   
  400 Bad Request 請求出現語法錯誤。 
  401 Unauthorized 客戶試圖未經授權訪問受密碼保護的頁面。應答中會包含一個WWW-Authenticate頭,瀏覽器據此顯示用戶名字/密碼對話框,然後在填寫合適的Authorization頭後再次發出請求。 
  403 Forbidden 資源不可用。服務器理解客戶的請求,但拒絕處理它。通常由於服務器上文件或目錄的權限設置導致。 
  404 Not Found 無法找到指定位置的資源。這也是一個常用的應答,HttpServletResponse專門提供了相應的方法:sendError(message)。 
  405 Method Not Allowed 請求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)對指定的資源不適用。(HTTP 1.1新) 
  406 Not Acceptable 指定的資源已經找到,但它的MIME類型和客戶在Accpet頭中所指定的不兼容(HTTP 1.1新)。 
  407 Proxy Authentication Required 類似於401,表示客戶必須先經過代理服務器的授權。(HTTP 1.1新) 
  408 Request Timeout 在服務器許可的等待時間內,客戶一直沒有發出任何請求。客戶可以在以後重複同一請求。(HTTP 1.1新) 
  409 Conflict 通常和PUT請求有關。由於請求和資源的當前狀態相沖突,因此請求不能成功。(HTTP 1.1新) 
  410 Gone 所請求的文檔已經不再可用,而且服務器不知道應該重定向到哪一個地址。它和404的不同在於,返回407表示文檔永久地離開了指定的位置,而404表示由於未知的原因文檔不可用。(HTTP 1.1新) 
  411 Length Required 服務器不能處理請求,除非客戶發送一個Content-Length頭。(HTTP 1.1新) 
  412 Precondition Failed 請求頭中指定的一些前提條件失敗(HTTP 1.1新)。 
  413 Request Entity Too Large 目標文檔的大小超過服務器當前願意處理的大小。如果服務器認爲自己能夠稍後再處理該請求,則應該提供一個Retry-After頭(HTTP 1.1新)。 
  414 Request URI Too Long URI太長(HTTP 1.1新)。 
  416 Requested Range Not Satisfiable 服務器不能滿足客戶在請求中指定的Range頭。(HTTP 1.1新) 
  500 Internal Server Error 服務器遇到了意料不到的情況,不能完成客戶的請求。 
  501 Not Implemented 服務器不支持實現請求所需要的功能。例如,客戶發出了一個服務器不支持的PUT請求。 
  502 Bad Gateway 服務器作爲網關或者代理時,爲了完成請求訪問下一個服務器,但該服務器返回了非法的應答。 
  503 Service Unavailable 服務器由於維護或者負載過重未能應答。例如,Servlet可能在數據庫連接池已滿的情況下返回503。服務器返回503時可以提供一個Retry-After頭。 
  504 Gateway Timeout 由作爲代理或網關的服務器使用,表示不能及時地從遠程服務器獲得應答。(HTTP 1.1新) 
  505 HTTP Version Not Supported 服務器不支持請求中所指明的HTTP版本。(HTTP 1.1新) 
   
   
     7.4 實例:訪問多個搜索引擎 
   
     下面這個例子用到了除200之外的另外兩個常見狀態代碼:302和404。302通過sendRedirect方法設置,404通過sendError方法設置。 
   
     在這個例子中,首先出現的HTML表單用來選擇搜索引擎、搜索字符串、每頁顯示的搜索結果數量。表單提交後,Servlet提取這三個變量,按照所選擇的搜索引擎的要求構造出包含這些變量的URL,然後把用戶重定向到這個URL。如果用戶不能正確地選擇搜索引擎,或者利用其他表單發送了一個不認識的搜索引擎名字,則返回一個提示搜索引擎找不到的404頁面。 
   
     SearchEngines.java 
   
     注意:這個Servlet要用到後面給出的SearchSpec類,SearchSpec的功能是構造適合不同搜索引擎的URL。 
  package hall; 
   
  import java.io.*; 
  import javax.servlet.*; 
  import javax.servlet.http.*; 
  import java.net.*; 
   
  public class SearchEngines extends HttpServlet { 
   public void doGet(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   // getParameter自動解碼URL編碼的查詢字符串。由於我們 
   // 要把查詢字符串發送給另一個服務器,因此再次使用 
   // URLEncoder進行URL編碼 
   String searchString = 
   URLEncoder.encode(request.getParameter("searchString")); 
   String numResults = 
   request.getParameter("numResults"); 
   String searchEngine = 
   request.getParameter("searchEngine"); 
   SearchSpec[] commonSpecs = SearchSpec.getCommonSpecs(); 
   for(int i=0; i<commonSpecs.length; i++) { 
   SearchSpec searchSpec = commonSpecs[i]; 
   if (searchSpec.getName().equals(searchEngine)) { 
   String url = 
   response.encodeURL(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); 
   } 
  } 
   
   
   
   
     SearchSpec.java 
  package hall; 
   
  class SearchSpec { 
   private String name, baseURL, numResultsSuffix; 
   
   private static SearchSpec[] commonSpecs = 
   { new SearchSpec("google", 
   "http://www.google.com/search?q=";, 
   "&num="), 
   new SearchSpec("infoseek", 
   "http://infoseek.go.com/Titles?qt=";, 
   "&nh="), 
   new SearchSpec("lycos", 
   "http://lycospro.lycos.com/cgi-bin/pursuit?query=";, 
   "&maxhits="), 
   new SearchSpec("hotbot", 
   "http://www.hotbot.com/?MT=";, 
   "&DC=") 
   }; 
   
   public SearchSpec(String name, 
   String baseURL, 
   String numResultsSuffix) { 
   this.name = name; 
   this.baseURL = baseURL; 
   this.numResultsSuffix = numResultsSuffix; 
   } 
   
   public String makeURL(String searchString, String numResults) { 
   return(baseURL + searchString + numResultsSuffix + numResults); 
   } 
   
   public String getName() { 
   return(name); 
   } 
   
   public static SearchSpec[] getCommonSpecs() { 
   return(commonSpecs); 
   } 
  } 
   
   
   
   
     SearchEngines.html 
   
     下面是調用上述Servlet的HTML表單。 
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
  <HTML> 
  <HEAD> 
   <TITLE>訪問多個搜索引擎</TITLE> 
  </HEAD> 
   
  <BODY BGCOLOR="#FDF5E6"> 
   
  <FORM ACTION="/servlet/hall.SearchEngines"> 
   <CENTER> 
   搜索關鍵字: 
   <INPUT TYPE="TEXT" NAME="searchString"><BR> 
   每頁顯示幾個查詢結果: 
   <INPUT TYPE="TEXT" NAME="numResults" 
   value=10 SIZE=3><BR> 
   <INPUT TYPE="RADIO" NAME="searchEngine" 
   value="google"> 
   Google | 
   <INPUT TYPE="RADIO" NAME="searchEngine" 
   value="infoseek"> 
   Infoseek | 
   <INPUT TYPE="RADIO" NAME="searchEngine" 
   value="lycos"> 
   Lycos | 
   <INPUT TYPE="RADIO" NAME="searchEngine" 
   value="hotbot"> 
   HotBot 
   <BR> 
   <INPUT TYPE="SUBMIT" value="Search"> 
   </CENTER> 
  </FORM> 
   
  </BODY> 
  </HTML> 
CGI變量概述 
   
     如果你是從傳統的CGI編程轉而學習Java Servlet,或許已經習慣了“CGI變量”這一概念。CGI變量彙集了各種有關請求的信息: 
   
  部分來自HTTP請求命令和請求頭,例如Content-Length頭; 
  部分來自Socket本身,例如主機的名字和IP地址; 
  也有部分與服務器安裝配置有關,例如URL到實際路徑的映射。 
     6.2 標準CGI變量的Servlet等價表示 
   
     下表假定request對象是提供給doGet和doPost方法的HttpServletRequest類型對象。 CGI變量 含義 從doGet或doPost訪問 
  AUTH_TYPE 如果提供了Authorization頭,這裏指定了具體的模式(basic或者digest)。 request.getAuthType() 
  CONTENT_LENGTH 只用於POST請求,表示所發送數據的字節數。 嚴格地講,等價的表達方式應該是String.valueOf(request.getContentLength())(返回一個字符串)。但更常見的是用request.getContentLength()返回含義相同的整數。 
  CONTENT_TYPE 如果指定的話,表示後面所跟數據的類型。 request.getContentType() 
  DOCUMENT_ROOT 與http://host/對應的路徑。 getServletContext().getRealPath("/") 
  注意低版本Servlet規範中的等價表達方式是request.getRealPath("/")。 
   
  HTTP_XXX_YYY 訪問任意HTTP頭。 request.getHeader("Xxx-Yyy") 
  PATH_INFO URL中的附加路徑信息,即URL中Servlet路徑之後、查詢字符串之前的那部分。 request.getPathInfo() 
  PATH_TRANSLATED 映射到服務器實際路徑之後的路徑信息。 request.getPathTranslated() 
  QUERY_STRING 這是字符串形式的附加到URL後面的查詢字符串,數據仍舊是URL編碼的。在Servlet中很少需要用到未經解碼的數據,一般使用getParameter訪問各個參數。 request.getQueryString() 
  REMOTE_ADDR 發出請求的客戶機的IP地址。 request.getRemoteAddr() 
  REMOTE_HOST 發出請求的客戶機的完整的域名,如java.sun.com。如果不能確定該域名,則返回IP地址。 request.getRemoteHost() 
  REMOTE_USER 如果提供了Authorization頭,則代表其用戶部分。它代表發出請求的用戶的名字。 request.getRemoteUser() 
  REQUEST_METHOD 請求類型。通常是GET或者POST。但偶爾也會出現HEAD,PUT, DELETE,OPTIONS,或者 TRACE. request.getMethod() 
  SCRIPT_NAME URL中調用Servlet的那一部分,不包含附加路徑信息和查詢字符串。 request.getServletPath() 
  SERVER_NAME Web服務器名字。 request.getServerName() 
  SERVER_PORT 服務器監聽的端口。 嚴格地說,等價表達應該是返回字符串的String.valueOf(request.getServerPort())。但經常使用返回整數值的request.getServerPort()。 
  SERVER_PROTOCOL 請求命令中的協議名字和版本(即HTTP/1.0或HTTP/1.1)。 request.getProtocol() 
  SERVER_SOFTWARE Servlet引擎的名字和版本。 getServletContext().getServerInfo() 
   
   
     6.3 實例:讀取CGI變量 
   
     下面這個Servlet創建一個表格,顯示除了HTTP_XXX_YYY之外的所有CGI變量。HTTP_XXX_YYY是HTTP請求頭信息,請參見上一節介紹。 
   
     ShowCGIVariables.java 
  package hall; 
   
  import java.io.*; 
  import javax.servlet.*; 
  import javax.servlet.http.*; 
  import java.util.*; 
   
  public class ShowCGIVariables extends HttpServlet { 
   public void doGet(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   response.setContentType("text/html"); 
   PrintWriter out = response.getWriter(); 
   String[][] variables = 
   { { "AUTH_TYPE", request.getAuthType() }, 
   { "CONTENT_LENGTH", String.valueOf(request.getContentLength()) }, 
   { "CONTENT_TYPE", request.getContentType() }, 
   { "DOCUMENT_ROOT", getServletContext().getRealPath("/") }, 
   { "PATH_INFO", request.getPathInfo() }, 
   { "PATH_TRANSLATED", request.getPathTranslated() }, 
   { "QUERY_STRING", request.getQueryString() }, 
   { "REMOTE_ADDR", request.getRemoteAddr() }, 
   { "REMOTE_HOST", request.getRemoteHost() }, 
   { "REMOTE_USER", request.getRemoteUser() }, 
   { "REQUEST_METHOD", request.getMethod() }, 
   { "SCRIPT_NAME", request.getServletPath() }, 
   { "SERVER_NAME", request.getServerName() }, 
   { "SERVER_PORT", String.valueOf(request.getServerPort()) }, 
   { "SERVER_PROTOCOL", request.getProtocol() }, 
   { "SERVER_SOFTWARE", getServletContext().getServerInfo() } 
   }; 
   String title = "顯示CGI變量"; 
   out.println(ServletUtilities.headWithTitle(title) + 
   "<BODY BGCOLOR=\"#FDF5E6\">\n" + 
   "<H1 ALIGN=CENTER>" + title + "</H1>\n" + 
   "<TABLE BORDER=1 ALIGN=CENTER>\n" + 
   "<TR BGCOLOR=\"#FFAD00\">\n" + 
   "<TH>CGI Variable Name<TH>value"); 
   for(int i=0; i<variables.length; i++) { 
   String varName = variables[i][0]; 
   String varvalue = variables[i][1]; 
   if (varvalue == null) 
   varvalue = "<I>Not specified</I>"; 
   out.println("<TR><TD>" + varName + "<TD>" + varvalue); 
   } 
   out.println("</TABLE></BODY></HTML>"); 
   } 
   
   public void doPost(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   doGet(request, response); 
   } 
  } 
HTTP請求頭概述 
   
     HTTP客戶程序(例如瀏覽器),向服務器發送請求的時候必須指明請求類型(一般是GET或者POST)。如有必要,客戶程序還可以選擇發送其他的請求頭。大多數請求頭並不是必需的,但Content-Length除外。對於POST請求來說Content-Length必須出現。 
   
     下面是一些最常見的請求頭: 
   
  Accept:瀏覽器可接受的MIME類型。 
  Accept-Charset:瀏覽器可接受的字符集。 
  Accept-Encoding:瀏覽器能夠進行解碼的數據編碼方式,比如gzip。Servlet能夠向支持gzip的瀏覽器返回經gzip編碼的HTML頁面。許多情形下這可以減少5到10倍的下載時間。 
  Accept-Language:瀏覽器所希望的語言種類,當服務器能夠提供一種以上的語言版本時要用到。 
  Authorization:授權信息,通常出現在對服務器發送的WWW-Authenticate頭的應答中。 
  Connection:表示是否需要持久連接。如果Servlet看到這裏的值爲“Keep-Alive”,或者看到請求使用的是HTTP 1.1(HTTP 1.1默認進行持久連接),它就可以利用持久連接的優點,當頁面包含多個元素時(例如Applet,圖片),顯著地減少下載所需要的時間。要實現這一點,Servlet需要在應答中發送一個Content-Length頭,最簡單的實現方法是:先把內容寫入ByteArrayOutputStream,然後在正式寫出內容之前計算它的大小。 
  Content-Length:表示請求消息正文的長度。 
  Cookie:這是最重要的請求頭信息之一,參見後面《Cookie處理》一章中的討論。 
  From:請求發送者的email地址,由一些特殊的Web客戶程序使用,瀏覽器不會用到它。 
  Host:初始URL中的主機和端口。 
  If-Modified-Since:只有當所請求的內容在指定的日期之後又經過修改才返回它,否則返回304“Not Modified”應答。 
  Pragma:指定“no-cache”值表示服務器必須返回一個刷新後的文檔,即使它是代理服務器而且已經有了頁面的本地拷貝。 
  Referer:包含一個URL,用戶從該URL代表的頁面出發訪問當前請求的頁面。 
  User-Agent:瀏覽器類型,如果Servlet返回的內容與瀏覽器類型有關則該值非常有用。 
  UA-Pixels,UA-Color,UA-OS,UA-CPU:由某些版本的IE瀏覽器所發送的非標準的請求頭,表示屏幕大小、顏色深度、操作系統和CPU類型。 
     有關HTTP頭完整、詳細的說明,請參見http://www.w3.org/Protocols/ 的HTTP規範。 
   
     5.2 在Servlet中讀取請求頭 
   
     在Servlet中讀取HTTP頭是非常方便的,只需要調用一下HttpServletRequest的getHeader方法即可。如果客戶請求中提供了指定的頭信息,getHeader返回對應的字符串;否則,返回null。部分頭信息經常要用到,它們有專用的訪問方法:getCookies方法返回Cookie頭的內容,經解析後存放在Cookie對象的數組中,請參見後面有關Cookie章節的討論;getAuthType和getRemoteUser方法分別讀取Authorization頭中的一部分內容;getDateHeader和getIntHeader方法讀取指定的頭,然後返回日期值或整數值。 
   
     除了讀取指定的頭之外,利用getHeaderNames還可以得到請求中所有頭名字的一個Enumeration對象。 
   
     最後,除了查看請求頭信息之外,我們還可以從請求主命令行獲得一些信息。getMethod方法返回請求方法,請求方法通常是GET或者POST,但也有可能是HEAD、PUT或者DELETE。getRequestURI方法返回URI(URI是URL的從主機和端口之後到表單數據之前的那一部分)。getRequestProtocol返回請求命令的第三部分,一般是“HTTP/1.0”或者“HTTP/1.1”。 
   
     5.3 實例:輸出所有的請求頭 
   
     下面的Servlet實例把所有接收到的請求頭和它的值以表格的形式輸出。另外,該Servlet還會輸出主請求命令的三個部分:請求方法,URI,協議/版本。 
   
     ShowRequestHeaders.java 
  package hall; 
   
  import java.io.*; 
  import javax.servlet.*; 
  import javax.servlet.http.*; 
  import java.util.*; 
   
  public class ShowRequestHeaders extends HttpServlet { 
   public void doGet(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   response.setContentType("text/html"); 
   PrintWriter out = response.getWriter(); 
   String title = "顯示所有請求頭"; 
   out.println(ServletUtilities.headWithTitle(title) + 
   "<BODY BGCOLOR=\"#FDF5E6\">\n" + 
   "<H1 ALIGN=CENTER>" + title + "</H1>\n" + 
   "<B>Request Method: </B>" + 
   request.getMethod() + "<BR>\n" + 
   "<B>Request URI: </B>" + 
   request.getRequestURI() + "<BR>\n" + 
   "<B>Request Protocol: </B>" + 
   request.getProtocol() + "<BR><BR>\n" + 
   "<TABLE BORDER=1 ALIGN=CENTER>\n" + 
   "<TR BGCOLOR=\"#FFAD00\">\n" + 
   "<TH>Header Name<TH>Header value"); 
   Enumeration headerNames = request.getHeaderNames(); 
   while(headerNames.hasMoreElements()) { 
   String headerName = (String)headerNames.nextElement(); 
   out.println("<TR><TD>" + headerName); 
   out.println(" <TD>" + request.getHeader(headerName)); 
   } 
   out.println("</TABLE>\n</BODY></HTML>"); 
   } 
   
   public void doPost(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   doGet(request, response); 
   } 
  } 
表單數據概述 
   
     如果你曾經使用過Web搜索引擎,或者瀏覽過在線書店、股票價格、機票信息,或許會留意到一些古怪的URL,比如“http://host/path?user=Marty+Hall&origin=bwi&dest=lax”。這個URL中位於問號後面的部分,即“user=Marty+Hall&origin=bwi&dest=lax”,就是表單數據,這是將Web頁面數據發送給服務器程序的最常用方法。對於GET請求,表單數據附加到URL的問號後面(如上例所示);對於POST請求,表單數據用一個單獨的行發送給服務器。 
   
     以前,從這種形式的數據提取出所需要的表單變量是CGI編程中最麻煩的事情之一。首先,GET請求和POST請求的數據提取方法不同:對於GET請求,通常要通過QUERY_STRING環境變量提取數據;對於POST請求,則一般通過標準輸入提取數據。第二,程序員必須負責在“&”符號處截斷變量名字-變量值對,再分離出變量名字(等號左邊)和變量值(等號右邊)。第三,必須對變量值進行URL反編碼操作。因爲發送數據的時候,字母和數字以原來的形式發送,但空格被轉換成加號,其他字符被轉換成“%XX”形式,其中XX是十六進制表示的字符ASCII(或者ISO Latin-1)編碼值。例如,如果HTML表單中名爲“users”的域值爲“~hall, ~gates, and ~mcnealy”,則實際向服務器發送的數據爲“users=%7Ehall%2C+%7Egates%2C+and+%7Emcnealy”。最後,即第四個導致解析表單數據非常困難的原因在於,變量值既可能被省略(如“param1=val1&param2=&param3=val3”),也有可能一個變量擁有一個以上的值,即同一個變量可能出現一次以上(如“param1=val1&param2=val2&param1=val3”)。 
   
     Java Servlet的好處之一就在於所有上述解析操作都能夠自動完成。只需要簡單地調用一下HttpServletRequest的getParameter方法、在調用參數中提供表單變量的名字(大小寫敏感)即可,而且GET請求和POST請求的處理方法完全相同。 
   
     getParameter方法的返回值是一個字符串,它是參數中指定的變量名字第一次出現所對應的值經反編碼得到得字符串(可以直接使用)。如果指定的表單變量存在,但沒有值,getParameter返回空字符串;如果指定的表單變量不存在,則返回null。如果表單變量可能對應多個值,可以用getParametervalues來取代getParameter。getParametervalues能夠返回一個字符串數組。 
   
     最後,雖然在實際應用中Servlet很可能只會用到那些已知名字的表單變量,但在調試環境中,獲得完整的表單變量名字列表往往是很有用的,利用getParamerterNames方法可以方便地實現這一點。getParamerterNames返回的是一個Enumeration,其中的每一項都可以轉換爲調用getParameter的字符串。 
   
     4.2 實例:讀取三個表單變量 
   
     下面是一個簡單的例子,它讀取三個表單變量param1、param2和param3,並以HTML列表的形式列出它們的值。請注意,雖然在發送應答內容之前必須指定應答類型(包括內容類型、狀態以及其他HTTP頭信息),但Servlet對何時讀取請求內容卻沒有什麼要求。 
   
     另外,我們也可以很容易地把Servlet做成既能處理GET請求,也能夠處理POST請求,這只需要在doPost方法中調用doGet方法,或者覆蓋service方法(service方法調用doGet、doPost、doHead等方法)。在實際編程中這是一種標準的方法,因爲它只需要很少的額外工作,卻能夠增加客戶端編碼的靈活性。 
   
     如果你習慣用傳統的CGI方法,通過標準輸入讀取POST數據,那麼在Servlet中也有類似的方法,即在HttpServletRequest上調用getReader或者getInputStream,但這種方法對普通的表單變量來說太麻煩。然而,如果是要上載文件,或者POST數據是通過專門的客戶程序而不是HTML表單發送,那麼就要用到這種方法。 
   
     注意用第二種方法讀取POST數據時,不能再用getParameter來讀取這些數據。 
   
     ThreeParams.java 
  package hall; 
   
  import java.io.*; 
  import javax.servlet.*; 
  import javax.servlet.http.*; 
  import java.util.*; 
   
  public class ThreeParams extends HttpServlet { 
   public void doGet(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   response.setContentType("text/html"); 
   PrintWriter out = response.getWriter(); 
   String title = "讀取三個請求參數"; 
   out.println(ServletUtilities.headWithTitle(title) + 
   "<BODY>\n" + 
   "<H1 ALIGN=CENTER>" + title + "</H1>\n" + 
   "<UL>\n" + 
   " <LI>param1: " 
   + request.getParameter("param1") + "\n" + 
   " <LI>param2: " 
   + request.getParameter("param2") + "\n" + 
   " <LI>param3: " 
   + request.getParameter("param3") + "\n" + 
   "</UL>\n" + 
   "</BODY></HTML>"); 
   } 
   
   public void doPost(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   doGet(request, response); 
   } 
  } 
   
   
   
   
     4.3 實例:輸出所有的表單數據 
   
     下面這個例子尋找表單所發送的所有變量名字,並把它們放入表格中,沒有值或者有多個值的變量都突出顯示。 
   
     首先,程序通過HttpServletRequest的getParameterNames方法得到所有的變量名字,getParameterNames返回的是一個Enumeration。接下來,程序用循環遍歷這個Enumeration,通過hasMoreElements確定何時結束循環,利用nextElement得到Enumeration中的各個項。由於nextElement返回的是一個Object,程序把它轉換成字符串後再用這個字符串來調用getParametervalues。 
   
     getParametervalues返回一個字符串數組,如果這個數組只有一個元素且等於空字符串,說明這個表單變量沒有值,Servlet以斜體形式輸出“No value”;如果數組元素個數大於1,說明這個表單變量有多個值,Servlet以HTML列表形式輸出這些值;其他情況下Servlet直接把變量值放入表格。 
   
     ShowParameters.java 
   
     注意,ShowParameters.java用到了前面介紹過的ServletUtilities.java。 
  package hall; 
   
  import java.io.*; 
  import javax.servlet.*; 
  import javax.servlet.http.*; 
  import java.util.*; 
   
  public class ShowParameters extends HttpServlet { 
   public void doGet(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   response.setContentType("text/html"); 
   PrintWriter out = response.getWriter(); 
   String title = "讀取所有請求參數"; 
   out.println(ServletUtilities.headWithTitle(title) + 
   "<BODY BGCOLOR=\"#FDF5E6\">\n" + 
   "<H1 ALIGN=CENTER>" + title + "</H1>\n" + 
   "<TABLE BORDER=1 ALIGN=CENTER>\n" + 
   "<TR BGCOLOR=\"#FFAD00\">\n" + 
   "<TH>參數名字<TH>參數值"); 
   Enumeration paramNames = request.getParameterNames(); 
   while(paramNames.hasMoreElements()) { 
   String paramName = (String)paramNames.nextElement(); 
   out.println("<TR><TD>" + paramName + "\n<TD>"); 
   String[] paramvalues = request.getParametervalues(paramName); 
   if (paramvalues.length == 1) { 
   String paramvalue = paramvalues[0]; 
   if (paramvalue.length() == 0) 
   out.print("<I>No value</I>"); 
   else 
   out.print(paramvalue); 
   } else { 
   out.println("<UL>"); 
   for(int i=0; i<paramvalues.length; i++) { 
   out.println("<LI>" + paramvalues[i]); 
   } 
   out.println("</UL>"); 
   } 
   } 
   out.println("</TABLE>\n</BODY></HTML>"); 
   } 
   
   public void doPost(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   doGet(request, response); 
   } 
  } 
   
   
   
   
     測試表單 
   
     下面是向上述Servlet發送數據的表單PostForm.html。就像所有包含密碼輸入域的表單一樣,該表單用POST方法發送數據。我們可以看到,在Servlet中同時實現doGet和doPost這兩種方法爲表單製作帶來了方便。 
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
  <HTML> 
  <HEAD> 
   <TITLE>示例表單</TITLE> 
  </HEAD> 
   
  <BODY BGCOLOR="#FDF5E6"> 
  <H1 ALIGN="CENTER">用POST方法發送數據的表單</H1> 
   
  <FORM ACTION="/servlet/hall.ShowParameters" 
   METHOD="POST"> 
   Item Number: 
   <INPUT TYPE="TEXT" NAME="itemNum"><BR> 
   Quantity: 
   <INPUT TYPE="TEXT" NAME="quantity"><BR> 
   Price Each: 
   <INPUT TYPE="TEXT" NAME="price" value="$"><BR> 
   <HR> 
   First Name: 
   <INPUT TYPE="TEXT" NAME="firstName"><BR> 
   Last Name: 
   <INPUT TYPE="TEXT" NAME="lastName"><BR> 
   Middle Initial: 
   <INPUT TYPE="TEXT" NAME="initial"><BR> 
   Shipping Address: 
   <TEXTAREA NAME="address" ROWS=3 COLS=40></TEXTAREA><BR> 
   Credit Card:<BR> 
   <INPUT TYPE="RADIO" NAME="cardType" 
   value="Visa">Visa<BR> 
   <INPUT TYPE="RADIO" NAME="cardType" 
   value="Master Card">Master Card<BR> 
   <INPUT TYPE="RADIO" NAME="cardType" 
   value="Amex">American Express<BR> 
   <INPUT TYPE="RADIO" NAME="cardType" 
   value="Discover">Discover<BR> 
   <INPUT TYPE="RADIO" NAME="cardType" 
   value="Java SmartCard">Java SmartCard<BR> 
   Credit Card Number: 
   <INPUT TYPE="PASSWORD" NAME="cardNum"><BR> 
   Repeat Credit Card Number: 
   <INPUT TYPE="PASSWORD" NAME="cardNum"><BR><BR> 
   <CENTER> 
   <INPUT TYPE="SUBMIT" value="Submit Order"> 
   </CENTER> 
  </FORM> 
   
  </BODY> 
  </HTML> 
Servlet基本結構 
   
     下面的代碼顯示了一個簡單Servlet的基本結構。該Servlet處理的是GET請求,所謂的GET請求,如果你不熟悉HTTP,可以把它看成是當用戶在瀏覽器地址欄輸入URL、點擊Web頁面中的鏈接、提交沒有指定METHOD的表單時瀏覽器所發出的請求。Servlet也可以很方便地處理POST請求。POST請求是提交那些指定了METHOD=“POST”的表單時所發出的請求,具體請參見稍後幾節的討論。 
  import java.io.*; 
  import javax.servlet.*; 
  import javax.servlet.http.*; 
   
  public class SomeServlet extends HttpServlet { 
   public void doGet(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   
   // 使用“request”讀取和請求有關的信息(比如Cookies) 
   // 和表單數據 
   
   // 使用“response”指定HTTP應答狀態代碼和應答頭 
   // (比如指定內容類型,設置Cookie) 
   
   PrintWriter out = response.getWriter(); 
   // 使用 "out"把應答內容發送到瀏覽器 
   } 
  } 
   
   
   
   
     如果某個類要成爲Servlet,則它應該從HttpServlet 繼承,根據數據是通過GET還是POST發送,覆蓋doGet、doPost方法之一或全部。doGet和doPost方法都有兩個參數,分別爲HttpServletRequest 類型和HttpServletResponse 類型。HttpServletRequest提供訪問有關請求的信息的方法,例如表單數據、HTTP請求頭等等。HttpServletResponse除了提供用於指定HTTP應答狀態(200,404等)、應答頭(Content-Type,Set-Cookie等)的方法之外,最重要的是它提供了一個用於向客戶端發送數據的PrintWriter 。對於簡單的Servlet來說,它的大部分工作是通過println語句生成向客戶端發送的頁面。 
   
     注意doGet和doPost拋出兩個異常,因此你必須在聲明中包含它們。另外,你還必須導入java.io包(要用到PrintWriter等類)、javax.servlet包(要用到HttpServlet等類)以及javax.servlet.http包(要用到HttpServletRequest類和HttpServletResponse類)。 
   
     最後,doGet和doPost這兩個方法是由service方法調用的,有時你可能需要直接覆蓋service方法,比如Servlet要處理GET和POST兩種請求時。 
   
     3.2 輸出純文本的簡單Servlet 
   
     下面是一個輸出純文本的簡單Servlet。 
   
     3.2.1 HelloWorld.java 
  package hall; 
   
  import java.io.*; 
  import javax.servlet.*; 
  import javax.servlet.http.*; 
   
  public class HelloWorld extends HttpServlet { 
   public void doGet(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   PrintWriter out = response.getWriter(); 
   out.println("Hello World"); 
   } 
  } 
   
   
   
   
     3.2.2 Servlet的編譯和安裝 
   
     不同的Web服務器上安裝Servlet的具體細節可能不同,請參考Web服務器文檔瞭解更權威的說明。假定使用Java Web Server(JWS)2.0,則Servlet應該安裝到JWS安裝目錄的servlets子目錄下。在本文中,爲了避免同一服務器上不同用戶的Servlet命名衝突,我們把所有Servlet都放入一個獨立的包hall中;如果你和其他人共用一個服務器,而且該服務器沒有“虛擬服務器”機制來避免這種命名衝突,那麼最好也使用包。把Servlet放入了包hall之後,HelloWorld.java實際上是放在servlets目錄的hall子目錄下。 
   
     大多數其他服務器的配置方法也相似,除了JWS之外,本文的Servlet和JSP示例已經在BEA WebLogic和IBM WebSphere 3.0下經過測試。WebSphere具有優秀的虛擬服務器機制,因此,如果只是爲了避免命名衝突的話並非一定要用包。 
   
     對於沒有使用過包的初學者,下面我們介紹編譯包裏面的類的兩種方法。 
   
     一種方法是設置CLASSPATH,使其指向實際存放Servlet的目錄的上一級目錄(Servlet主目錄),然後在該目錄中按正常的方式編譯。例如,如果Servlet的主目錄是C:\JavaWebServer\servlets,包的名字(即主目錄下的子目錄名字)是hall,在Windows下,編譯過程如下: 
   DOS> set CLASSPATH=C:\JavaWebServer\servlets;%CLASSPATH% 
   DOS> cd C:\JavaWebServer\servlets\hall 
   DOS> javac YourServlet.java 
   
   
   
     第二種編譯包裏面的Servlet的方法是進入Servlet主目錄,執行“javac directory\YourServlet.java”(Windows)或者“javac directory/YourServlet.java”(Unix)。例如,再次假定Servlet主目錄是C:\JavaWebServer\servlets,包的名字是hall,在Windows中編譯過程如下: 
   DOS> cd C:\JavaWebServer\servlets 
   DOS> javac hall\YourServlet.java 
   
   
   
     注意在Windows下,大多數JDK 1.1版本的javac要求目錄名字後面加反斜槓(\)。JDK1.2已經改正這個問題,然而由於許多Web服務器仍舊使用JDK 1.1,因此大量的Servlet開發者仍舊在使用JDK 1.1。 
   
     最後,Javac還有一個高級選項用於支持源代碼和.class文件的分開放置,即你可以用javac的“-d”選項把.class文件安裝到Web服務器所要求的目錄。 
   
     3.2.3 運行Servlet 
   
     在Java Web Server下,Servlet應該放到JWS安裝目錄的servlets子目錄下,而調用Servlet的URL是http://host/servlet/ServletName。注意子目錄的名字是servlets(帶“s”),而URL使用的是“servlet”。由於HelloWorld Servlet放入包hall,因此調用它的URL應該是http://host/servlet/hall.HelloWorld。在其他的服務器上,安裝和調用Servlet的方法可能略有不同。 
   
     大多數Web服務器還允許定義Servlet的別名,因此Servlet也可能用http://host/any-path/any-file.html形式的URL調用。具體如何進行配置完全依賴於服務器類型,請參考服務器文檔瞭解細節。 
   
     3.3 輸出HTML的Servlet 
   
     大多數Servlet都輸出HTML,而不象上例一樣輸出純文本。要輸出HTML還有兩個額外的步驟要做:告訴瀏覽器接下來發送的是HTML;修改println語句構造出合法的HTML頁面。 
   
     第一步通過設置Content-Type(內容類型)應答頭完成。一般地,應答頭可以通過HttpServletResponse的setHeader方法設置,但由於設置內容類型是一個很頻繁的操作,因此Servlet API提供了一個專用的方法setContentType。注意設置應答頭應該在通過PrintWriter發送內容之前進行。下面是一個實例: 
   
     HelloWWW.java 
  package hall; 
   
  import java.io.*; 
  import javax.servlet.*; 
  import javax.servlet.http.*; 
   
  public class HelloWWW extends HttpServlet { 
   public void doGet(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   response.setContentType("text/html"); 
   PrintWriter out = response.getWriter(); 
   out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " + 
   "Transitional//EN\">\n" + 
   "<HTML>\n" + 
   "<HEAD><TITLE>Hello WWW</TITLE></HEAD>\n" + 
   "<BODY>\n" + 
   "<H1>Hello WWW</H1>\n" + 
   "</BODY></HTML>"); 
   } 
  } 
   
   
   
   
     3.4 幾個HTML工具函數 
   
     通過println語句輸出HTML並不方便,根本的解決方法是使用JavaServer Pages(JSP)。然而,對於標準的Servlet來說,由於Web頁面中有兩個部分(DOCTYPE和HEAD)一般不會改變,因此可以用工具函數來封裝生成這些內容的代碼。 
   
     雖然大多數主流瀏覽器都會忽略DOCTYPE行,但嚴格地說HTML規範是要求有DOCTYPE行的,它有助於HTML語法檢查器根據所聲明的HTML版本檢查HTML文檔合法性。在許多Web頁面中,HEAD部分只包含<TITLE>。雖然許多有經驗的編寫者都會在HEAD中包含許多META標記和樣式聲明,但這裏只考慮最簡單的情況。 
   
     下面的Java方法只接受頁面標題爲參數,然後輸出頁面的DOCTYPE、HEAD、TITLE部分。清單如下: 
   
     ServletUtilities.java 
  package hall; 
   
  public class ServletUtilities { 
   public static final String DOCTYPE = 
   "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">"; 
   
   public static String headWithTitle(String title) { 
   return(DOCTYPE + "\n" + 
   "<HTML>\n" + 
   "<HEAD><TITLE>" + title + "</TITLE></HEAD>\n"); 
   } 
   
   // 其他工具函數的代碼在本文後面介紹 
  } 
   
   
   
   
     HelloWWW2.java 
   
     下面是應用了ServletUtilities之後重寫HelloWWW類得到的HelloWWW2: 
  package hall; 
   
  import java.io.*; 
  import javax.servlet.*; 
  import javax.servlet.http.*; 
   
  public class HelloWWW2 extends HttpServlet { 
   public void doGet(HttpServletRequest request, 
   HttpServletResponse response) 
   throws ServletException, IOException { 
   response.setContentType("text/html"); 
   PrintWriter out = response.getWriter(); 
   out.println(ServletUtilities.headWithTitle("Hello WWW") + 
   "<BODY>\n" + 
   "<H1>Hello WWW</H1>\n" + 
   "</BODY></HTML>"); 
   } 
  } 
安裝Servlet和JSP開發工具 
   
     要學習Servlet和JSP開發,首先你必須準備一個符合Java Servlet 2.1/2.2和JavaServer Pages1.0/1.1規範的開發環境。Sun提供免費的JavaServer Web Development Kit(JSWDK),可以從http://java.sun.com/products/servlet/ 下載。 
   
     安裝好JSWDK之後,你還要告訴javac,在編譯文件的時候到哪裏去尋找Servlet和JSP類。JSWDK安裝指南對此有詳細說明,但主要就是把servlet.jar和jsp.jar加入CLASSPATH。CLASSPATH是一個指示Java如何尋找類文件的環境變量,如果不設置CLASSPATH,Java在當前目錄和標準系統庫中尋找類;如果你自己設置了CLASSPATH,不要忘記包含當前目錄(即在CLASSPATH中包含“.”)。 
   
     另外,爲了避免和其他開發者安裝到同一Web服務器上的Servlet產生命名衝突,最好把自己的Servlet放入包裏面。此時,把包層次結構中的頂級目錄也加入CLASSPATH會帶來不少方便。請參見下文具體說明。 
   
     2.2 安裝支持Servlet的Web服務器 
   
     除了開發工具之外,你還要安裝一個支持Java Servlet的Web服務器,或者在現有的Web服務器上安裝Servlet軟件包。如果你使用的是最新的Web服務器或應用服務器,很可能它已經有了所有必需的軟件。請查看Web服務器的文檔,或訪問http://java.sun.com/products/servlet/industry.html 查看支持Servlet的服務器軟件清單。 
   
     雖然最終運行Servlet的往往是商業級的服務器,但是開始學習的時候,用一個能夠在臺式機上運行的免費系統進行開發和測試也足夠了。下面是幾種當前最受歡迎的產品。 
   
  Apache Tomcat. 
   
  Tomcat是Servlet 2.2和JSP 1.1規範的官方參考實現。Tomcat既可以單獨作爲小型Servlet、JSP測試服務器,也可以集成到Apache Web服務器。直到2000年早期,Tomcat還是唯一的支持Servlet 2.2和JSP 1.1規範的服務器,但已經有許多其它服務器宣佈提供這方面的支持。 
   
  Tomcat和Apache一樣是免費的。不過,快速、穩定的Apache服務器安裝和配置起來有點麻煩,Tomcat也有同樣的缺點。和其他商業級Servlet引擎相比,配置Tomcat的工作量顯然要多一點。具體請參見http://jakarta.apache.org/ 。 
   
   
  JavaServer Web Development Kit (JSWDK). 
   
  JSWDK是Servlet 2.1和JSP 1.0的官方參考實現。把Servlet和JSP應用部署到正式運行它們的服務器之前,JSWDK可以單獨作爲小型的Servlet、JSP測試服務器。JSWDK也是免費的,而且具有很好的穩定性,但它的安裝和配置也較爲複雜。具體請參見http://java.sun.com/products/servlet/download.html 。 
   
   
  Allaire JRun. 
   
  JRun是一個Servlet和JSP引擎,它可以集成到Netscape Enterprise或FastTrack Server、IIS、Microsoft Personal Web Server、版本較低的Apache、O'eilly的WebSite或者StarNine Web STAR。最多支持5個併發連接的限制版本是免費的,商業版本中不存在這個限制,而且增加了遠程管理控制檯之類的功能。具體請參見http://www.allaire.com/products/jrun/ 。 
   
   
  New Atlanta 的ServletExec 
   
  ServletExec是一個快速的Servlet和JSP引擎,它可以集成到大多數流行的Web服務器,支持平臺包括Solaris、Windows、MacOS、HP-UX和Linux。ServletExec可以免費下載和使用,但許多高級功能和管理工具只有在購買了許可之後纔可以使用。New Atlanta還提供一個免費的Servlet調試器,該調試器可以在許多流行的Java IDE下工作。具體請參見http://newatlanta.com/ 。 
   
   
  Gefion的LiteWebServer (LWS) 
   
  LWS是一個支持Servlet 2.2和JSP 1.1的免費小型Web服務器。 Gefion還有一個免費的WAICoolRunner插件,利用該插件可以爲Netscape FastTrack和Enterprise Server增加Servlet 2.2和JSP 1.1支持。具體請參見http://www.gefionsoftware.com/ 。 
   
   
  Sun的Java Web Server. 
   
  該服務器全部用Java寫成,而且是首先提供Servlet 2.1和JSP 1.0規範完整支持的Web服務器之一。雖然Sun現在已轉向Netscape/I-Planet Server,不再發展Java Web Server,但它仍舊是一個廣受歡迎的Servlet、JSP學習平臺。要得到免費試用版本,請訪問http://www.sun.com/software/jwebserver/try/ 。 
Java Servlet及其特點 
   
     Servlet是Java技術對CGI編程的回答。Servlet程序在服務器端運行,動態地生成Web頁面。與傳統的CGI和許多其他類似CGI的技術相比,Java Servlet具有更高的效率,更容易使用,功能更強大,具有更好的可移植性,更節省投資(更重要的是, Servlet程序員收入要比Perl程序員高:-): 
   
  高效。 
   
  在傳統的CGI中,每個請求都要啓動一個新的進程,如果CGI程序本身的執行時間較短,啓動進程所需要的開銷很可能反而超過實際執行時間。而在Servlet中,每個請求由一個輕量級的Java線程處理(而不是重量級的操作系統進程)。 
  在傳統CGI中,如果有N個併發的對同一CGI程序的請求,則該CGI程序的代碼在內存中重複裝載了N次;而對於Servlet,處理請求的是N個線程,只需要一份Servlet類代碼。在性能優化方面,Servlet也比CGI有着更多的選擇,比如緩衝以前的計算結果,保持數據庫連接的活動,等等。 
   
   
  方便。 
   
  Servlet提供了大量的實用工具例程,例如自動地解析和解碼HTML表單數據、讀取和設置HTTP頭、處理Cookie、跟蹤會話狀態等。 
   
   
  功能強大。 
   
  在Servlet中,許多使用傳統CGI程序很難完成的任務都可以輕鬆地完成。例如,Servlet能夠直接和Web服務器交互,而普通的CGI程序不能。Servlet還能夠在各個程序之間共享數據,使得數據庫連接池之類的功能很容易實現。 
   
   
  可移植性好。 
   
  Servlet用Java編寫,Servlet API具有完善的標準。因此,爲I-Planet Enterprise Server寫的Servlet無需任何實質上的改動即可移植到Apache、Microsoft IIS或者WebStar。幾乎所有的主流服務器都直接或通過插件支持Servlet。 
   
   
  節省投資。 
   
  不僅有許多廉價甚至免費的Web服務器可供個人或小規模網站使用,而且對於現有的服務器,如果它不支持Servlet的話,要加上這部分功能也往往是免費的(或只需要極少的投資)。 
     1.2 JSP及其特點 
   
     JavaServer Pages(JSP)是一種實現普通靜態HTML和動態HTML混合編碼的技術,有關JSP基礎概念的說明請參見《JSP技術簡介 》。 
   
     許多由CGI程序生成的頁面大部分仍舊是靜態HTML,動態內容只在頁面中有限的幾個部分出現。但是包括Servlet在內的大多數CGI技術及其變種,總是通過程序生成整個頁面。JSP使得我們可以分別創建這兩個部分。例如,下面就是一個簡單的JSP頁面: 
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
  <HTML> 
  <HEAD><TITLE>歡迎訪問網上商店</TITLE></HEAD> 
  <BODY> 
  <H1>歡迎</H1> 
  <SMALL>歡迎, 
  <!-- 首次訪問的用戶名字爲"New User" --> 
  <% out.println(Utils.getUserNameFromCookie(request)); %> 
  要設置帳號信息,請點擊 
  <A HREF="Account-Settings.html">這裏</A></SMALL> 
  <P> 
  頁面的其餘內容。. 
  </BODY></HTML> 
   
   
   
     下面是JSP和其他類似或相關技術的一個簡單比較: 
   
  JSP和Active Server Pages(ASP)相比 
   
  Microsoft的ASP是一種和JSP類似的技術。JSP和ASP相比具有兩方面的優點。首先,動態部分用Java編寫,而不是VB Script或其他Microsoft語言,不僅功能更強大而且更易於使用。第二,JSP應用可以移植到其他操作系統和非Microsoft的Web服務器上。 
   
   
  JSP和純Servlet相比 
   
  JSP並沒有增加任何本質上不能用Servlet實現的功能。但是,在JSP中編寫靜態HTML更加方便,不必再用 println語句來輸出每一行HTML代碼。更重要的是,藉助內容和外觀的分離,頁面製作中不同性質的任務可以方便地分開:比如,由頁面設計專家進行HTML設計,同時留出供Servlet程序員插入動態內容的空間。 
   
   
  JSP和服務器端包含(Server-Side Include,SSI)相比 
   
  SSI是一種受到廣泛支持的在靜態HTML中引入外部代碼的技術。JSP在這方面的支持更爲完善,因爲它可以用Servlet而不是獨立的程序來生成動態內容。另外,SSI實際上只用於簡單的包含,而不是面向那些能夠處理表單數據、訪問數據庫的“真正的”程序。 
   
   
  JSP和javascript相比 
   
  javascript能夠在客戶端動態地生成HTML。雖然javascript很有用,但它只能處理以客戶端環境爲基礎的動態信息。除了Cookie之外,HTTP狀態和表單提交數據對javascript來說都是不可用的。另外,由於是在客戶端運行,javascript不能訪問服務器端資源,比如數據庫、目錄信息等等。 

發佈了53 篇原創文章 · 獲贊 1 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章