Servlet與JSP教程

- 中國WEB開發者網絡 (http://www.webasp.net)
-- 技術教程 (http://www.webasp.net/article/)
--- Servlet與JSP教程 (http://www.webasp.net/article/22/21468.htm)
-- 作者:未知
-- 發佈日期: 2005-04-30
Servlet與JSP教程(1)-Servlet和JSP概述  一、Servlet和JSP概述  1.1 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不能訪問服務器端資源,比如數據庫、目錄信息等等。Servlet與JSP教程(2)-設置開發、運行環境  二、設置開發、運行環境  2.1 安裝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/ 。Servlet與JSP教程(3)  如果某個類要成為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教程(4)  四、處理表單數據  4.1 表單數據概述  如果你曾經使用過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與JSP教程(5)-讀取HTTP請求頭  五、讀取HTTP請求頭  5.1 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);   }   }Servlet與JSP教程(6)-訪問CGI變量  六、訪問CGI變量  6.1 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);   }   }Servlet與JSP教程(7)-HTTP應答狀態  七、HTTP應答狀態  7.1 狀態代碼概述  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>Servlet與JSP教程(8)  八、設置HTTP應答頭  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>Servlet與JSP教程(9)  九、處理Cookie   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);   }   }Servlet與JSP教程(10)-會話狀態  十、會話狀態  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);   }   }Servlet與JSP教程(11)-JSP及語法概要  十一、JSP及語法概要  11.1 概述  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直接發送到客戶端。此外,模板文本也可以用任何現有的頁面製作工具來編寫。  唯一的例外在於,如果要輸出“<%”,則模板文本中應該寫成“<\%”。Servlet與JSP教程(12)-腳本元素、指令和預定義變量  十二、  12.1 JSP腳本元素  JSP腳本元素用來插入Java代碼,這些Java代碼將出現在由當前JSP頁面生成的Servlet中。腳本元素有三種格式:  表達式格式<%= expression %>:計算表達式並輸出其結果。  Scriptlet格式<% code %>:把代碼插入到Servlet的service方法。  聲明格式<%! code %>:把聲明加入到Servlet類(在任何方法之外)。  下面我們詳細說明它們的用法。  12.1.1 JSP表達式  JSP表達式用來把Java數據直接插入到輸出。其語法如下:  <%= Java Expression %>  計算Java表達式得到的結果被轉換成字符串,然後插入到頁面。計算在運行時進行(頁面被請求時),因此可以訪問和請求有關的全部信息。例如,下面的代碼顯示頁面被請求的日期/時間:  Current time: <%= new java.util.Date() %>  為簡化這些表達式,JSP預定義了一組可以直接使用的對象變量。後面我們將詳細介紹這些隱含聲明的對象,但對於JSP表達式來說,最重要的幾個對象及其類型如下:  request:HttpServletRequest;   response:HttpServletResponse;   session:和request關聯的HttpSession   out:PrintWriter(帶緩衝的版本,JspWriter),用來把輸出發送到客戶端  下面是一個例子:  Your hostname: <%= request.getRemoteHost() %>  最後,如果使用XML的話,JSP表達式也可以寫成下面這種形式:  <jsp:expression>  Java Expression   </jsp:expression>  請記住XML元素和HTML不一樣。XML是大小寫敏感的,因此務必使用小寫。有關XML語法的說明,請參見《XML教程》  12.1.2 JSP Scriptlet   如果你要完成的任務比插入簡單的表達式更加複雜,可以使用JSP Scriptlet。JSP Scriptlet允許你把任意的Java代碼插入Servlet。JSP Scriptlet語法如下:  <% Java Code %>  和JSP表達式一樣,Scriptlet也可以訪問所有預定義的變量。例如,如果你要向結果頁面輸出內容,可以使用out變量:  <%   String queryData = request.getQueryString();   out.println("Attached GET data: " + queryData);   %>  注意Scriptlet中的代碼將被照搬到Servlet內,而Scriptlet前面和後面的靜態HTML(模板文本)將被轉換成println語句。這就意味著,Scriptlet內的Java語句並非一定要是完整的,沒有關閉的塊將影響Scriptlet外的靜態HTML。例如,下面的JSP片斷混合了模板文本和Scriptlet:  <% if (Math.random() <0.5) { %>  Have a <B>nice</B>day!   <% } else { %>  Have a <B>lousy</B>day!   <% } %>  上述JSP代碼將被轉換成如下Servlet代碼:  if (Math.random() <0.5) {   out.println("Have a <B>nice</B>day!");   } else {   out.println("Have a <B>lousy</B>day!");   }   如果要在Scriptlet內部使用字符“%>”,必須寫成“%\>”。另外,請注意<% code %>的XML等價表達是:  <jsp:scriptlet>  Code   </jsp:scriptlet>  12.1.3 JSP聲明  JSP聲明用來定義插入Servlet類的方法和成員變量,其語法如下:  <%! Java Code %>  由於聲明不會有任何輸出,因此它們往往和JSP表達式或Scriptlet結合在一起使用。例如,下面的JSP代碼片斷輸出自從服務器啟動(或Servlet類被改動並重新裝載以來)當前頁面被請求的次數:  <%! private int accessCount = 0; %>  自從服務器啟動以來頁面訪問次數為:  <%= ++accessCount %>  和Scriptlet一樣,如果要使用字符串“%>”,必須使用“%\>”代替。最後,<%! code %>的XML等價表達方式為:  <jsp:declaration>  Code   </jsp:declaration>  12.2 JSP指令  JSP指令影響Servlet類的整體結構,它的語法一般如下:  <%@ directive attribute="value" %>  另外,也可以把同一指令的多個屬性結合起來,例如:  <%@ directive attribute1="value1"   attribute2="value2"   ...   attributeN="valueN" %>  JSP指令分為兩種類型:第一是page指令,用來完成下面這類任務:導入指定的類,自定義Servlet的超類,等等;第二是include指令,用來在JSP文件轉換成Servlet時引入其他文件。JSP規範也提到了taglib指令,其目的是讓JSP開發者能夠自己定義標記,但JSP 1.0不支持該指令,有希望它將成為JSP 1.1的主要改進之一。  12.2.1 page指令  page指令的作用是定義下面一個或多個屬性,這些屬性大小寫敏感。  import="package.class",或者import="package.class1,...,package.classN":  用於指定導入哪些包,例如:<%@ page import="java.util.*" %>。import是唯一允許出現一次以上的屬性。  contentType="MIME-Type" 或contentType="MIME-Type; charset=Character-Set":  該屬性指定輸出的MIME類型。默認是text/html。例如,下面這個指令:  <%@ page contentType="text/plain" %>。  和下面的Scriptlet效果相同:  <% response.setContentType("text/plain"); %>  isThreadSafe="true|false"   默認值true表明Servlet按照標準的方式處理,即假定開發者已經同步對實例變量的訪問,由單個Servlet實例同時地處理多個請求。如果取值false,表明Servlet應該實現SingleThreadModel,請求或者是逐個進入,或者多個並行的請求分別由不同的Servlet實例處理。  session="true|false"   默認值true表明預定義變量session(類型為HttpSession)應該綁定到已有的會話,如果不存在已有的會話,則新建一個並綁定session變量。如果取值false,表明不會用到會話,試圖訪問變量session將導致JSP轉換成Servlet時出錯。  buffer="size kb|none"   該屬性指定JspWrite out的緩存大小。默認值和服務器有關,但至少應該是8 KB。  autoflush="true|false"   默認值true表明如果緩存已滿則刷新它。autoflush很少取false值,false值表示如果緩存已滿則拋出異常。如果buffer="none",autoflush不能取false值。  extends="package.class"   該屬性指出將要生成的Servlet使用哪個超類。使用該屬性應當十分小心,因為服務器可能已經在用自定義的超類。  info="message"   該屬性定義一個可以通過getServletInfo方法提取的字符串。  errorPage="url"   該屬性指定一個JSP頁面,所有未被當前頁面捕獲的異常均由該頁面處理。  isErrorPage="true|false"   該屬性指示當前頁面是否可以作為另一JSP頁面的錯誤處理頁面。默認值false。  language="java"   該屬性用來指示所使用的語言。目前沒有必要關注這個屬性,因為默認的Java是當前唯一可用的語言。  定義指令的XML語法為:  <jsp:directive.directiveType attribute=value />  例如,下面這個指令:  <%@ page import="java.util.*" %>  它的XML等價表達是:  <jsp:directive.page import="java.util.*" />  12.2.2 include指令  include指令用於JSP頁面轉換成Servlet時引入其他文件。該指令語法如下:  <%@ include file="relative url" %>  這裡所指定的URL是和發出引用指令的JSP頁面相對的URL,然而,與通常意義上的相對URL一樣,你可以利用以“/”開始的URL告訴系統把URL視為從Web服務器根目錄開始。包含文件的內容也是JSP代碼,即包含文件可以包含靜態HTML、腳本元素、JSP指令和動作。  例如,許多網站的每個頁面都有一個小小的導航條。由於HTML框架存在不少問題,導航條往往用頁面頂端或左邊的一個表格製作,同一份HTML代碼重複出現在整個網站的每個頁面上。include指令是實現該功能的非常理想的方法。使用include指令,開發者不必再把導航HTML代碼拷貝到每個文件中,從而可以更輕鬆地完成維護工作。  由於include指令是在JSP轉換成Servlet的時候引入文件,因此如果導航條改變了,所有使用該導航條的JSP頁面都必須重新轉換成Servlet。如果導航條改動不頻繁,而且你希望包含操作具有盡可能好的效率,使用include指令是最好的選擇。然而,如果導航條改動非常頻繁,你可以使用jsp:include動作。jsp:include動作在出現對JSP頁面請求的時候才會引用指定的文件,請參見本文後面的具體說明。  12.3 實例:腳本元素和指令的應用  下面是一個使用JSP表達式、Scriptlet、聲明、指令的簡單例子。  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">  <HTML>  <HEAD>  <TITLE>JavaServer Pages</TITLE>  </HEAD>  <BODY BGCOLOR="#FDF5E6" TEXT="#000000" LINK="#0000EE"   VLINK="#551A8B" ALINK="#FF0000">  <CENTER>  <TABLE BORDER=5 BGCOLOR="#EF8429">  <TR><TH CLASS="TITLE">  JSP應用實例</TABLE>  </CENTER>  <P>  下面是一些利用各種JSP功能生成的動態內容:   <UL>  <LI><B>表達式.</B><BR>  你的主機名: <%= request.getRemoteHost() %>.   <LI><B>JSP Scriptlet.</B><BR>  <% out.println("查詢字符串: " +   request.getQueryString()); %>  <LI><B>聲明(和表達式).</B><BR>  <%! private int accessCount = 0; %>  服務器啟動以來訪問次數: <%= ++accessCount %>  <LI><B>指令(和表達式).</B><BR>  <%@ page import = "java.util.*" %>  當前日期: <%= new Date() %>  </UL>  </BODY>  </HTML>  12.4 JSP預定義變量  為了簡化JSP表達式和Scriptlet的代碼,JSP提供了8個預先定義的變量(或稱為隱含對像)。這些變量是request、response、out、session、application、config、pageContext和page。  12.4.1 request   這是和請求關聯的HttpServletRequest,通過它可以查看請求參數(調用getParameter),請求類型(GET,POST,HEAD,等),以及請求的HTTP頭(Cookie,Referer,等)。嚴格說來,如果請求所用的是HTTP之外的其他協議,request可以是ServletRequest的子類(而不是HttpServletRequest),但在實踐中幾乎不會用到。  12.4.2 response   這是和應答關聯的HttpServletResponse。注意,由於輸出流(參見下面的out)是帶緩衝的,因此,如果已經向客戶端發送了輸出內容,普通Servlet不允許再設置HTTP狀態代碼,但在JSP中卻是合法的。  12.4.3 out   這是用來向客戶端發送內容的PrintWriter。然而,為了讓response對像更為實用,out是帶緩存功能的PrintWriter,即JspWriter。JSP允許通過page指令的buffer屬性調整緩存的大小,甚至允許關閉緩存。  out一般只在Scriptlet內使用,這是因為JSP表達式是自動發送到輸出流的,很少需要顯式地引用out。  12.4.4 session   這是和請求關聯的HttpSession對象。前面我們已經介紹過會話的自動創建,我們知道,即使不存在session引用,這個對象也是自動綁定的。但有一個例外,這就是如果你用page指令的session屬性關閉了會話,此時對session變量的引用將導致JSP頁面轉換成Servlet時出錯。  12.4.5 application   這是一個ServletContext,也可以通過getServletConfig().getContext()獲得。  12.4.6 config   這是當前頁面的ServletConfig對象。  12.4.7 pageContext   主要用來管理頁面的屬性。  12.4.8 page   它是this的同義詞,當前用處不大。它是為了Java不再是唯一的JSP編程語言而準備的佔位符。Servlet與JSP教程(13)-JSP動作  十三、JSP動作  JSP動作利用XML語法格式的標記來控制Servlet引擎的行為。利用JSP動作可以動態地插入文件、重用JavaBean組件、把用戶重定向到另外的頁面、為Java插件生成HTML代碼。  JSP動作包括:  jsp:include:在頁面被請求的時候引入一個文件。  jsp:useBean:尋找或者實例化一個JavaBean。  jsp:setProperty:設置JavaBean的屬性。  jsp:getProperty:輸出某個JavaBean的屬性。  jsp:forward:把請求轉到一個新的頁面。  jsp:plugin:根據瀏覽器類型為Java插件生成OBJECT或EMBED標記。  13.1 jsp:include動作  該動作把指定文件插入正在生成的頁面。其語法如下:  <jsp:include page="relative URL" flush="true" />  前面已經介紹過include指令,它是在JSP文件被轉換成Servlet的時候引入文件,而這裡的jsp:include動作不同,插入文件的時間是在頁面被請求的時候。jsp:include動作的文件引入時間決定了它的效率要稍微差一點,而且被引用文件不能包含某些JSP代碼(例如不能設置HTTP頭),但它的靈活性卻要好得多。  例如,下面的JSP頁面把4則新聞摘要插入一個“What`s New ?”頁面。改變新聞摘要時只需改變這四個文件,而主JSP頁面卻可以不作修改:  WhatsNew.jsp   <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">  <HTML>  <HEAD>  <TITLE>What`s New</TITLE>  </HEAD>  <BODY BGCOLOR="#FDF5E6" TEXT="#000000" LINK="#0000EE"   VLINK="#551A8B" ALINK="#FF0000">  <CENTER>  <TABLE BORDER=5 BGCOLOR="#EF8429">  <TR><TH CLASS="TITLE">  What`s New at JspNews.com</TABLE>  </CENTER>  <P>  Here is a summary of our four most recent news stories:   <OL>  <LI><jsp:include page="news/Item1.html" flush="true"/>  <LI><jsp:include page="news/Item2.html" flush="true"/>  <LI><jsp:include page="news/Item3.html" flush="true"/>  <LI><jsp:include page="news/Item4.html" flush="true"/>  </OL>  </BODY>  </HTML>  13.2 jsp:useBean動作  jsp:useBean動作用來裝載一個將在JSP頁面中使用的JavaBean。這個功能非常有用,因為它使得我們既可以發揮Java組件重用的優勢,同時也避免了損失JSP區別於Servlet的方便性。jsp:useBean動作最簡單的語法為:  <jsp:useBean id="name" class="package.class" />  這行代碼的含義是:“創建一個由class屬性指定的類的實例,然後把它綁定到其名字由id屬性給出的變量上”。不過,就像我們接下來會看到的,定義一個scope屬性可以讓Bean關聯到更多的頁面。此時,jsp:useBean動作只有在不存在同樣id和scope的Bean時才創建新的對象實例,同時,獲得現有Bean的引用就變得很有必要。  獲得Bean實例之後,要修改Bean的屬性既可以通過jsp:setProperty動作進行,也可以在Scriptlet中利用id屬性所命名的對象變量,通過調用該對象的方法顯式地修改其屬性。這使我們想起,當我們說“某個Bean有一個類型為X的屬性foo”時,就意味著“這個類有一個返回值類型為X的getFoo方法,還有一個setFoo方法以X類型的值為參數”。  有關jsp:setProperty動作的詳細情況在後面討論。但現在必須瞭解的是,我們既可以通過jsp:setProperty動作的value屬性直接提供一個值,也可以通過param屬性聲明Bean的屬性值來自指定的請求參數,還可以列出Bean屬性表明它的值應該來自請求參數中的同名變量。  在JSP表達式或Scriptlet中讀取Bean屬性通過調用相應的getXXX方法實現,或者更一般地,使用jsp:getProperty動作。  注意包含Bean的類文件應該放到服務器正式存放Java類的目錄下,而不是保留給修改後能夠自動裝載的類的目錄。例如,對於Java Web Server來說,Bean和所有Bean用到的類都應該放入classes目錄,或者封裝進jar文件後放入lib目錄,但不應該放到servlets下。  下面是一個很簡單的例子,它的功能是裝載一個Bean,然後設置/讀取它的message屬性。  BeanTest.jsp   <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">  <HTML>  <HEAD>  <TITLE>Reusing JavaBeans in JSP</TITLE>  </HEAD>  <BODY>  <CENTER>  <TABLE BORDER=5>  <TR><TH CLASS="TITLE">  Reusing JavaBeans in JSP</TABLE>  </CENTER>  <P>  <jsp:useBean id="test" class="hall.SimpleBean" />  <jsp:setProperty name="test"   property="message"   value="Hello WWW" />  <H1>Message: <I>  <jsp:getProperty name="test" property="message" />  </I></H1>  </BODY>  </HTML>  SimpleBean.java   BeanTest頁面用到了一個SimpleBean。SimpleBean的代碼如下:  package hall;   public class SimpleBean {   private String message = "No message specified";   public String getMessage() {   return(message);   }   public void setMessage(String message) {   this.message = message;   }   }   13.3 關於jsp:useBean的進一步說明  使用Bean最簡單的方法是先用下面的代碼裝載Bean:  <jsp:useBean id="name" class="package.class" />  然後通過jsp:setProperty和jsp:getProperty修改和提取Bean的屬性。不過有兩點必須注意。第一,我們還可以用下面這種格式實例化Bean:  <jsp:useBean ...>  Body   </jsp:useBean>  它的意思是,只有當第一次實例化Bean時才執行Body部分,如果是利用現有的Bean實例則不執行Body部分。正如下面將要介紹的,jsp:useBean並非總是意味著創建一個新的Bean實例。  第二,除了id和class外,jsp:useBean還有其他三個屬性,即:scope,type,beanName。下表簡要說明這些屬性的用法。屬性用法  id 命名引用該Bean的變量。如果能夠找到id和scope相同的Bean實例,jsp:useBean動作將使用已有的Bean實例而不是創建新的實例。  class 指定Bean的完整包名。  scope 指定Bean在哪種上下文內可用,可以取下面的四個值之一:page,request,session和application。  默認值是page,表示該Bean只在當前頁面內可用(保存在當前頁面的PageContext內)。  request表示該Bean在當前的客戶請求內有效(保存在ServletRequest對像內)。  session表示該Bean對當前HttpSession內的所有頁面都有效。  最後,如果取值application,則表示該Bean對所有具有相同ServletContext的頁面都有效。  scope之所以很重要,是因為jsp:useBean只有在不存在具有相同id和scope的對象時才會實例化新的對象;如果已有id和scope都相同的對象則直接使用已有的對象,此時jsp:useBean開始標記和結束標記之間的任何內容都將被忽略。  type 指定引用該對象的變量的類型,它必須是Bean類的名字、超類名字、該類所實現的接口名字之一。請記住變量的名字是由id屬性指定的。  beanName 指定Bean的名字。如果提供了type屬性和beanName屬性,允許省略class屬性。  13.4 jsp:setProperty動作  jsp:setProperty用來設置已經實例化的Bean對象的屬性,有兩種用法。首先,你可以在jsp:useBean元素的外面(後面)使用jsp:setProperty,如下所示:  <jsp:useBean id="myName" ... />  ...   <jsp:setProperty name="myName"   property="someProperty" ... />  此時,不管jsp:useBean是找到了一個現有的Bean,還是新創建了一個Bean實例,jsp:setProperty都會執行。第二種用法是把jsp:setProperty放入jsp:useBean元素的內部,如下所示:  <jsp:useBean id="myName" ... >  ...   <jsp:setProperty name="myName"   property="someProperty" ... />  </jsp:useBean>  此時,jsp:setProperty只有在新建Bean實例時才會執行,如果是使用現有實例則不執行jsp:setProperty。  jsp:setProperty動作有下面四個屬性:屬性說明  name name屬性是必需的。它表示要設置屬性的是哪個Bean。  property property屬性是必需的。它表示要設置哪個屬性。有一個特殊用法:如果property的值是“*”,表示所有名字和Bean屬性名字匹配的請求參數都將被傳遞給相應的屬性set方法。  value value屬性是可選的。該屬性用來指定Bean屬性的值。字符串數據會在目標類中通過標準的valueOf方法自動轉換成數字、boolean、Boolean、byte、Byte、char、Character。例如,boolean和Boolean類型的屬性值(比如“true”)通過Boolean.valueOf轉換,int和Integer類型的屬性值(比如“42”)通過Integer.valueOf轉換。  value和param不能同時使用,但可以使用其中任意一個。  param param是可選的。它指定用哪個請求參數作為Bean屬性的值。如果當前請求沒有參數,則什麼事情也不做,系統不會把null傳遞給Bean屬性的set方法。因此,你可以讓Bean自己提供默認屬性值,只有當請求參數明確指定了新值時才修改默認屬性值。  例如,下面的代碼片斷表示:如果存在numItems請求參數的話,把numberOfItems屬性的值設置為請求參數numItems的值;否則什麼也不做。  <jsp:setProperty name="orderBean"   property="numberOfItems"   param="numItems" />  如果同時省略value和param,其效果相當於提供一個param且其值等於property的值。進一步利用這種借助請求參數和屬性名字相同進行自動賦值的思想,你還可以在property(Bean屬性的名字)中指定“*”,然後省略value和param。此時,服務器會查看所有的Bean屬性和請求參數,如果兩者名字相同則自動賦值。  下面是一個利用JavaBean計算素數的例子。如果請求中有一個numDigits參數,則該值被傳遞給Bean的numDigits屬性;numPrimes也類似。  JspPrimes.jsp   <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">  <HTML>  <HEAD>  <TITLE>在JSP中使用JavaBean</TITLE>  </HEAD>  <BODY>  <CENTER>  <TABLE BORDER=5>  <TR><TH CLASS="TITLE">  在JSP中使用JavaBean</TABLE>  </CENTER>  <P>  <jsp:useBean id="primeTable" class="hall.NumberedPrimes" />  <jsp:setProperty name="primeTable" property="numDigits" />  <jsp:setProperty name="primeTable" property="numPrimes" />  Some <jsp:getProperty name="primeTable" property="numDigits" />  digit primes:   <jsp:getProperty name="primeTable" property="numberedList" />  </BODY>  </HTML>  註:NumberedPrimes的代碼略。  13.5 jsp:getProperty動作  jsp:getProperty動作提取指定Bean屬性的值,轉換成字符串,然後輸出。jsp:getProperty有兩個必需的屬性,即:name,表示Bean的名字;property,表示要提取哪個屬性的值。下面是一個例子,更多的例子可以在前文找到。  <jsp:useBean id="itemBean" ... />  ...   <UL>  <LI>Number of items:   <jsp:getProperty name="itemBean" property="numItems" />  <LI>Cost of each:   <jsp:getProperty name="itemBean" property="unitCost" />  </UL>  13.6 jsp:forward動作  jsp:forward動作把請求轉到另外的頁面。jsp:forward標記只有一個屬性page。page屬性包含的是一個相對URL。page的值既可以直接給出,也可以在請求的時候動態計算,如下面的例子所示:  <jsp:forward page="/utils/errorReporter.jsp" />  <jsp:forward page="<%= someJavaExpression %>" />  13.7 jsp:plugin動作  jsp:plugin動作用來根據瀏覽器的類型,插入通過Java插件運行Java Applet所必需的OBJECT或EMBED元素。  附錄:JSP註釋和字符引用約定  下面是一些特殊的標記或字符,你可以利用它們插入註釋或可能被視為具有特殊含義的字符。語法用途  <%-- comment --%>JSP註釋,也稱為“隱藏註釋”。JSP引擎將忽略它。標記內的所有JSP腳本元素、指令和動作都將不起作用。  <!-- comment -->HTML註釋,也稱為“輸出的註釋”,直接出現在結果HTML文檔中。標記內的所有JSP腳本元素、指令和動作正常執行。  <\% 在模板文本(靜態HTML)中實際上希望出現“<%”的地方使用。  %\>在腳本元素內實際上希望出現“%>”的地方使用。  \` 使用單引號的屬性內的單引號。不過,你既可以使用單引號也可以使用雙引號,而另外一種引號將具有普通含義。    \" 使用雙引號的屬性內的雙引號。參見“\`”的說明Java Servlet和JSP教程目錄(1-11)1 概述       1.1 Java Servlet及其特點       1.2 JSP及其特點\r    2 安裝       2.1 安裝Servlet和JSP開發工具       2.2 安裝支持Servlet的Web服務器      3 Servlet       3.1 Servlet基本結構       3.2 輸出純文本的簡單Servlet           3.2.1 HelloWorld.java           3.2.2 Servlet的編譯和安裝           3.2.3 運行Servlet       3.3 輸出HTML的Servlet       3.4 幾個HTML工具函數     4 表單       4.1 表單數據概述       4.2 實例:讀取三個表單變量       4.3 實例:輸出所有的表單數據     5 HTTP請求頭       5.1 HTTP請求頭概述       5.2 在Servlet中讀取請求頭       5.3 實例:輸出所有的請求頭     6 CGI變量       6.1 CGI變量概述       6.2 標準CGI變量的Servlet等價表示       6.3 實例:讀取CGI變量     7 狀態代碼       7.1 狀態代碼概述       7.2 設置狀態代碼       7.3 HTTP 1.1狀態代碼及其含義       7.4 實例:訪問多個搜索引擎     8 HTTP應答頭       8.1 HTTP應答頭概述       8.2 實例:內容改變時自動刷新頁面\    9 Cookie       9.1 Cookie概述       9.2 Servlet的Cookie API           9.2.1 創建Cookie           9.2.2 讀取和設置Cookie屬性           9.2.3 在應答頭中設置Cookie           9.2.4 讀取保存到客戶端的Cookie       9.3 幾個Cookie工具函數           9.3.1 獲取指定名字的Cookie值           9.3.2自動保存的Cookie       9.4 實例:定制的搜索引擎界面     10 會話        10.1 會話狀態概述        10.2 會話狀態跟蹤API             10.2.1 查看當前請求的會話對像             10.2.2 查看和會話有關的信息             10.2.3 在會話對像中保存數據        10.3 實例:顯示會話信息     11 JSP        11.1 JSP概述        11.2 JSP語法概要表        11.3 關於模板文本(靜態HTML)    12 JSP基礎內容及指令\r       12.1 JSP腳本元素             12.1.1 JSP表達式            12.1.2 JSP Scriptlet             12.1.3 JSP聲明        12.2 JSP指令             12.2.1 page指令             12.2.2 include指令        12.3 實例:腳本元素和指令的應用        12.4 JSP預定義變量             12.4.1 request             12.4.2 response             12.4.3 out             12.4.4 session             12.4.5 application             12.4.6 config             12.4.7 pageContext             12.4.8 page      13 JSP高級內容        13.1 jsp:include動作         13.2 jsp:useBean動作         13.3 關於jsp:useBean的進一步說明         13.4 jsp:setProperty動作         13.5 jsp:getProperty動作\r        13.6 jsp:forward動作         13.7 jsp:plugin動作      附錄:JSP註釋和字符引用約定 

webasp.net