一、Servlet和JSP概述
? 作 者 : 仙人掌工作室
??
??
? 1.1 Java Servlet及其特點(diǎn)
??
? Servlet是Java技術(shù)對(duì)CGI編程的回答。Servlet程序在服務(wù)器端運(yùn)行,動(dòng)態(tài)地生成Web頁面。與傳統(tǒng)的CGI和許多其他類似CGI的技術(shù)相比,Java Servlet具有更高的效率,更容易使用,功能更強(qiáng)大,具有更好的可移植性,更節(jié)省投資(更重要的是, Servlet程序員收入要比Perl程序員高:-):
??
? 高效。
??
? 在傳統(tǒng)的CGI中,每個(gè)請(qǐng)求都要啟動(dòng)一個(gè)新的進(jìn)程,如果CGI程序本身的執(zhí)行時(shí)間較短,啟動(dòng)進(jìn)程所需要的開銷很可能反而超過實(shí)際執(zhí)行時(shí)間。而在Servlet中,每個(gè)請(qǐng)求由一個(gè)輕量級(jí)的Java線程處理(而不是重量級(jí)的操作系統(tǒng)進(jìn)程)。
? 在傳統(tǒng)CGI中,如果有N個(gè)并發(fā)的對(duì)同一CGI程序的請(qǐng)求,則該CGI程序的代碼在內(nèi)存中重復(fù)裝載了N次;而對(duì)于Servlet,處理請(qǐng)求的是N個(gè)線程,只需要一份Servlet類代碼。在性能優(yōu)化方面,Servlet也比CGI有著更多的選擇,比如緩沖以前的計(jì)算結(jié)果,保持?jǐn)?shù)據(jù)庫連接的活動(dòng),等等。
??
??
? 方便。
??
? Servlet提供了大量的實(shí)用工具例程,例如自動(dòng)地解析和解碼HTML表單數(shù)據(jù)、讀取和設(shè)置HTTP頭、處理Cookie、跟蹤會(huì)話狀態(tài)等。
??
??
? 功能強(qiáng)大。
??
? 在Servlet中,許多使用傳統(tǒng)CGI程序很難完成的任務(wù)都可以輕松地完成。例如,Servlet能夠直接和Web服務(wù)器交互,而普通的CGI程序不能。Servlet還能夠在各個(gè)程序之間共享數(shù)據(jù),使得數(shù)據(jù)庫連接池之類的功能很容易實(shí)現(xiàn)。
??
??
? 可移植性好。
??
? Servlet用Java編寫,Servlet API具有完善的標(biāo)準(zhǔn)。因此,為I-Planet Enterprise Server寫的Servlet無需任何實(shí)質(zhì)上的改動(dòng)即可移植到Apache、Microsoft IIS或者WebStar。幾乎所有的主流服務(wù)器都直接或通過插件支持Servlet。
??
??
? 節(jié)省投資。
??
? 不僅有許多廉價(jià)甚至免費(fèi)的Web服務(wù)器可供個(gè)人或小規(guī)模網(wǎng)站使用,而且對(duì)于現(xiàn)有的服務(wù)器,如果它不支持Servlet的話,要加上這部分功能也往往是免費(fèi)的(或只需要極少的投資)。
? 1.2 JSP及其特點(diǎn)
??
? JavaServer Pages(JSP)是一種實(shí)現(xiàn)普通靜態(tài)HTML和動(dòng)態(tài)HTML混合編碼的技術(shù),有關(guān)JSP基礎(chǔ)概念的說明請(qǐng)參見《JSP技術(shù)簡(jiǎn)介 》。
??
? 許多由CGI程序生成的頁面大部分仍舊是靜態(tài)HTML,動(dòng)態(tài)內(nèi)容只在頁面中有限的幾個(gè)部分出現(xiàn)。但是包括Servlet在內(nèi)的大多數(shù)CGI技術(shù)及其變種,總是通過程序生成整個(gè)頁面。JSP使得我們可以分別創(chuàng)建這兩個(gè)部分。例如,下面就是一個(gè)簡(jiǎn)單的JSP頁面:
? <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
? <HTML>
? <HEAD><TITLE>歡迎訪問網(wǎng)上商店</TITLE></HEAD>
? <BODY>
? <H1>歡迎</H1>
? <SMALL>歡迎,
? <!-- 首次訪問的用戶名字為"New User" -->
? <% out.println(Utils.getUserNameFromCookie(request)); %>
? 要設(shè)置帳號(hào)信息,請(qǐng)點(diǎn)擊
? <A HREF="Account-Settings.html">這里</A></SMALL>
? <P>
? 頁面的其余內(nèi)容。.
? </BODY></HTML>
??
??
??
? 下面是JSP和其他類似或相關(guān)技術(shù)的一個(gè)簡(jiǎn)單比較:
??
? JSP和Active Server Pages(ASP)相比
??
? Microsoft的ASP是一種和JSP類似的技術(shù)。JSP和ASP相比具有兩方面的優(yōu)點(diǎn)。首先,動(dòng)態(tài)部分用Java編寫,而不是VB Script或其他Microsoft語言,不僅功能更強(qiáng)大而且更易于使用。第二,JSP應(yīng)用可以移植到其他操作系統(tǒng)和非Microsoft的Web服務(wù)器上。
??
??
? JSP和純Servlet相比
??
? JSP并沒有增加任何本質(zhì)上不能用Servlet實(shí)現(xiàn)的功能。但是,在JSP中編寫靜態(tài)HTML更加方便,不必再用 println語句來輸出每一行HTML代碼。更重要的是,借助內(nèi)容和外觀的分離,頁面制作中不同性質(zhì)的任務(wù)可以方便地分開:比如,由頁面設(shè)計(jì)專家進(jìn)行HTML設(shè)計(jì),同時(shí)留出供Servlet程序員插入動(dòng)態(tài)內(nèi)容的空間。
??
??
? JSP和服務(wù)器端包含(Server-Side Include,SSI)相比
??
? SSI是一種受到廣泛支持的在靜態(tài)HTML中引入外部代碼的技術(shù)。JSP在這方面的支持更為完善,因?yàn)樗梢杂肧ervlet而不是獨(dú)立的程序來生成動(dòng)態(tài)內(nèi)容。另外,SSI實(shí)際上只用于簡(jiǎn)單的包含,而不是面向那些能夠處理表單數(shù)據(jù)、訪問數(shù)據(jù)庫的“真正的”程序。
??
??
? JSP和JavaScript相比
??
? JavaScript能夠在客戶端動(dòng)態(tài)地生成HTML。雖然JavaScript很有用,但它只能處理以客戶端環(huán)境為基礎(chǔ)的動(dòng)態(tài)信息。除了Cookie之外,HTTP狀態(tài)和表單提交數(shù)據(jù)對(duì)JavaScript來說都是不可用的。另外,由于是在客戶端運(yùn)行,JavaScript不能訪問服務(wù)器端資源,比如數(shù)據(jù)庫、目錄信息等等。
?2.1 安裝Servlet和JSP開發(fā)工具
??
? 要學(xué)習(xí)Servlet和JSP開發(fā),首先你必須準(zhǔn)備一個(gè)符合Java Servlet 2.1/2.2和JavaServer Pages1.0/1.1規(guī)范的開發(fā)環(huán)境。Sun提供免費(fèi)的JavaServer Web Development Kit(JSWDK),可以從
http://java.sun.com/products/servlet/ 下載。
??
? 安裝好JSWDK之后,你還要告訴javac,在編譯文件的時(shí)候到哪里去尋找Servlet和JSP類。JSWDK安裝指南對(duì)此有詳細(xì)說明,但主要就是把servlet.jar和jsp.jar加入CLASSPATH。CLASSPATH是一個(gè)指示Java如何尋找類文件的環(huán)境變量,如果不設(shè)置CLASSPATH,Java在當(dāng)前目錄和標(biāo)準(zhǔn)系統(tǒng)庫中尋找類;如果你自己設(shè)置了CLASSPATH,不要忘記包含當(dāng)前目錄(即在CLASSPATH中包含“.”)。
??
? 另外,為了避免和其他開發(fā)者安裝到同一Web服務(wù)器上的Servlet產(chǎn)生命名沖突,最好把自己的Servlet放入包里面。此時(shí),把包層次結(jié)構(gòu)中的頂級(jí)目錄也加入CLASSPATH會(huì)帶來不少方便。請(qǐng)參見下文具體說明。
??
? 2.2 安裝支持Servlet的Web服務(wù)器
??
? 除了開發(fā)工具之外,你還要安裝一個(gè)支持Java Servlet的Web服務(wù)器,或者在現(xiàn)有的Web服務(wù)器上安裝Servlet軟件包。如果你使用的是最新的Web服務(wù)器或應(yīng)用服務(wù)器,很可能它已經(jīng)有了所有必需的軟件。請(qǐng)查看Web服務(wù)器的文檔,或訪問
http://java.sun.com/products/servlet/industry.html 查看支持Servlet的服務(wù)器軟件清單。
??
? 雖然最終運(yùn)行Servlet的往往是商業(yè)級(jí)的服務(wù)器,但是開始學(xué)習(xí)的時(shí)候,用一個(gè)能夠在臺(tái)式機(jī)上運(yùn)行的免費(fèi)系統(tǒng)進(jìn)行開發(fā)和測(cè)試也足夠了。下面是幾種當(dāng)前最受歡迎的產(chǎn)品。
??
? Apache Tomcat.
??
? Tomcat是Servlet 2.2和JSP 1.1規(guī)范的官方參考實(shí)現(xiàn)。Tomcat既可以單獨(dú)作為小型Servlet、JSP測(cè)試服務(wù)器,也可以集成到Apache Web服務(wù)器。直到2000年早期,Tomcat還是唯一的支持Servlet 2.2和JSP 1.1規(guī)范的服務(wù)器,但已經(jīng)有許多其它服務(wù)器宣布提供這方面的支持。
??
? Tomcat和Apache一樣是免費(fèi)的。不過,快速、穩(wěn)定的Apache服務(wù)器安裝和配置起來有點(diǎn)麻煩,Tomcat也有同樣的缺點(diǎn)。和其他商業(yè)級(jí)Servlet引擎相比,配置Tomcat的工作量顯然要多一點(diǎn)。具體請(qǐng)參見
http://jakarta.apache.org/ 。
??
??
? JavaServer Web Development Kit (JSWDK).
??
? JSWDK是Servlet 2.1和JSP 1.0的官方參考實(shí)現(xiàn)。把Servlet和JSP應(yīng)用部署到正式運(yùn)行它們的服務(wù)器之前,JSWDK可以單獨(dú)作為小型的Servlet、JSP測(cè)試服務(wù)器。JSWDK也是免費(fèi)的,而且具有很好的穩(wěn)定性,但它的安裝和配置也較為復(fù)雜。具體請(qǐng)參見
http://java.sun.com/products/servlet/download.html 。
??
??
? Allaire JRun.
??
? JRun是一個(gè)Servlet和JSP引擎,它可以集成到Netscape Enterprise或FastTrack Server、IIS、Microsoft Personal Web Server、版本較低的Apache、O'eilly的WebSite或者StarNine Web STAR。最多支持5個(gè)并發(fā)連接的限制版本是免費(fèi)的,商業(yè)版本中不存在這個(gè)限制,而且增加了遠(yuǎn)程管理控制臺(tái)之類的功能。具體請(qǐng)參見
http://www.allaire.com/products/jrun/ 。
??
??
? New Atlanta 的ServletExec
??
? ServletExec是一個(gè)快速的Servlet和JSP引擎,它可以集成到大多數(shù)流行的Web服務(wù)器,支持平臺(tái)包括Solaris、Windows、MacOS、HP-UX和Linux。ServletExec可以免費(fèi)下載和使用,但許多高級(jí)功能和管理工具只有在購買了許可之后才可以使用。New Atlanta還提供一個(gè)免費(fèi)的Servlet調(diào)試器,該調(diào)試器可以在許多流行的Java IDE下工作。具體請(qǐng)參見
http://newatlanta.com/ 。
??
??
? Gefion的LiteWebServer (LWS)
??
? LWS是一個(gè)支持Servlet 2.2和JSP 1.1的免費(fèi)小型Web服務(wù)器。 Gefion還有一個(gè)免費(fèi)的WAICoolRunner插件,利用該插件可以為Netscape FastTrack和Enterprise Server增加Servlet 2.2和JSP 1.1支持。具體請(qǐng)參見
http://www.gefionsoftware.com/ 。
??
??
? Sun的Java Web Server.
??
? 該服務(wù)器全部用Java寫成,而且是首先提供Servlet 2.1和JSP 1.0規(guī)范完整支持的Web服務(wù)器之一。雖然Sun現(xiàn)在已轉(zhuǎn)向Netscape/I-Planet Server,不再發(fā)展Java Web Server,但它仍舊是一個(gè)廣受歡迎的Servlet、JSP學(xué)習(xí)平臺(tái)。要得到免費(fèi)試用版本,請(qǐng)?jiān)L問
http://www.sun.com/software/jwebserver/try/ 。
?
3.1 Servlet基本結(jié)構(gòu)
??
? 下面的代碼顯示了一個(gè)簡(jiǎn)單Servlet的基本結(jié)構(gòu)。該Servlet處理的是GET請(qǐng)求,所謂的GET請(qǐng)求,如果你不熟悉HTTP,可以把它看成是當(dāng)用戶在瀏覽器地址欄輸入U(xiǎn)RL、點(diǎn)擊Web頁面中的鏈接、提交沒有指定METHOD的表單時(shí)瀏覽器所發(fā)出的請(qǐng)求。Servlet也可以很方便地處理POST請(qǐng)求。POST請(qǐng)求是提交那些指定了METHOD=“POST”的表單時(shí)所發(fā)出的請(qǐng)求,具體請(qǐng)參見稍后幾節(jié)的討論。
? 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”讀取和請(qǐng)求有關(guān)的信息(比如Cookies)
?? // 和表單數(shù)據(jù)
??
?? // 使用“response”指定HTTP應(yīng)答狀態(tài)代碼和應(yīng)答頭
?? // (比如指定內(nèi)容類型,設(shè)置Cookie)
??
?? PrintWriter out = response.getWriter();
?? // 使用 "out"把應(yīng)答內(nèi)容發(fā)送到瀏覽器
?? }
? }
??
??
??
??
? 如果某個(gè)類要成為Servlet,則它應(yīng)該從HttpServlet 繼承,根據(jù)數(shù)據(jù)是通過GET還是POST發(fā)送,覆蓋doGet、doPost方法之一或全部。doGet和doPost方法都有兩個(gè)參數(shù),分別為HttpServletRequest 類型和HttpServletResponse 類型。HttpServletRequest提供訪問有關(guān)請(qǐng)求的信息的方法,例如表單數(shù)據(jù)、HTTP請(qǐng)求頭等等。HttpServletResponse除了提供用于指定HTTP應(yīng)答狀態(tài)(200,404等)、應(yīng)答頭(Content-Type,Set-Cookie等)的方法之外,最重要的是它提供了一個(gè)用于向客戶端發(fā)送數(shù)據(jù)的PrintWriter 。對(duì)于簡(jiǎn)單的Servlet來說,它的大部分工作是通過println語句生成向客戶端發(fā)送的頁面。
??
? 注意doGet和doPost拋出兩個(gè)異常,因此你必須在聲明中包含它們。另外,你還必須導(dǎo)入java.io包(要用到PrintWriter等類)、javax.servlet包(要用到HttpServlet等類)以及javax.servlet.http包(要用到HttpServletRequest類和HttpServletResponse類)。
??
? 最后,doGet和doPost這兩個(gè)方法是由service方法調(diào)用的,有時(shí)你可能需要直接覆蓋service方法,比如Servlet要處理GET和POST兩種請(qǐng)求時(shí)。
??
? 3.2 輸出純文本的簡(jiǎn)單Servlet
??
? 下面是一個(gè)輸出純文本的簡(jiǎn)單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服務(wù)器上安裝Servlet的具體細(xì)節(jié)可能不同,請(qǐng)參考Web服務(wù)器文檔了解更權(quán)威的說明。假定使用Java Web Server(JWS)2.0,則Servlet應(yīng)該安裝到JWS安裝目錄的servlets子目錄下。在本文中,為了避免同一服務(wù)器上不同用戶的Servlet命名沖突,我們把所有Servlet都放入一個(gè)獨(dú)立的包hall中;如果你和其他人共用一個(gè)服務(wù)器,而且該服務(wù)器沒有“虛擬服務(wù)器”機(jī)制來避免這種命名沖突,那么最好也使用包。把Servlet放入了包hall之后,HelloWorld.java實(shí)際上是放在servlets目錄的hall子目錄下。
??
? 大多數(shù)其他服務(wù)器的配置方法也相似,除了JWS之外,本文的Servlet和JSP示例已經(jīng)在BEA WebLogic和IBM WebSphere 3.0下經(jīng)過測(cè)試。WebSphere具有優(yōu)秀的虛擬服務(wù)器機(jī)制,因此,如果只是為了避免命名沖突的話并非一定要用包。
??
? 對(duì)于沒有使用過包的初學(xué)者,下面我們介紹編譯包里面的類的兩種方法。
??
? 一種方法是設(shè)置CLASSPATH,使其指向?qū)嶋H存放Servlet的目錄的上一級(jí)目錄(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的方法是進(jìn)入Servlet主目錄,執(zhí)行“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下,大多數(shù)JDK 1.1版本的javac要求目錄名字后面加反斜杠(\)。JDK1.2已經(jīng)改正這個(gè)問題,然而由于許多Web服務(wù)器仍舊使用JDK 1.1,因此大量的Servlet開發(fā)者仍舊在使用JDK 1.1。
??
? 最后,Javac還有一個(gè)高級(jí)選項(xiàng)用于支持源代碼和.class文件的分開放置,即你可以用javac的“-d”選項(xiàng)把.class文件安裝到Web服務(wù)器所要求的目錄。
??
? 3.2.3 運(yùn)行Servlet
??
? 在Java Web Server下,Servlet應(yīng)該放到JWS安裝目錄的servlets子目錄下,而調(diào)用Servlet的URL是
http://host/servlet/ServletName。注意子目錄的名字是servlets(帶“s”),而URL使用的是“servlet”。由于HelloWorld Servlet放入包hall,因此調(diào)用它的URL應(yīng)該是
http://host/servlet/hall.HelloWorld。在其他的服務(wù)器上,安裝和調(diào)用Servlet的方法可能略有不同。
??
? 大多數(shù)Web服務(wù)器還允許定義Servlet的別名,因此Servlet也可能用
http://host/any-path/any-file.html形式的URL調(diào)用。具體如何進(jìn)行配置完全依賴于服務(wù)器類型,請(qǐng)參考服務(wù)器文檔了解細(xì)節(jié)。
??
? 3.3 輸出HTML的Servlet
??
? 大多數(shù)Servlet都輸出HTML,而不象上例一樣輸出純文本。要輸出HTML還有兩個(gè)額外的步驟要做:告訴瀏覽器接下來發(fā)送的是HTML;修改println語句構(gòu)造出合法的HTML頁面。
??
? 第一步通過設(shè)置Content-Type(內(nèi)容類型)應(yīng)答頭完成。一般地,應(yīng)答頭可以通過HttpServletResponse的setHeader方法設(shè)置,但由于設(shè)置內(nèi)容類型是一個(gè)很頻繁的操作,因此Servlet API提供了一個(gè)專用的方法setContentType。注意設(shè)置應(yīng)答頭應(yīng)該在通過PrintWriter發(fā)送內(nèi)容之前進(jìn)行。下面是一個(gè)實(shí)例:
??
? 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 幾個(gè)HTML工具函數(shù)
??
? 通過println語句輸出HTML并不方便,根本的解決方法是使用JavaServer Pages(JSP)。然而,對(duì)于標(biāo)準(zhǔn)的Servlet來說,由于Web頁面中有兩個(gè)部分(DOCTYPE和HEAD)一般不會(huì)改變,因此可以用工具函數(shù)來封裝生成這些內(nèi)容的代碼。
??
? 雖然大多數(shù)主流瀏覽器都會(huì)忽略DOCTYPE行,但嚴(yán)格地說HTML規(guī)范是要求有DOCTYPE行的,它有助于HTML語法檢查器根據(jù)所聲明的HTML版本檢查HTML文檔合法性。在許多Web頁面中,HEAD部分只包含<TITLE>。雖然許多有經(jīng)驗(yàn)的編寫者都會(huì)在HEAD中包含許多META標(biāo)記和樣式聲明,但這里只考慮最簡(jiǎn)單的情況。
??
? 下面的Java方法只接受頁面標(biāo)題為參數(shù),然后輸出頁面的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");
?? }
??
?? // 其他工具函數(shù)的代碼在本文后面介紹
? }
??
??
??
??
? HelloWWW2.java
??
? 下面是應(yīng)用了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>");
?? }
? }
4.1 表單數(shù)據(jù)概述
??
? 如果你曾經(jīng)使用過Web搜索引擎,或者瀏覽過在線書店、股票價(jià)格、機(jī)票信息,或許會(huì)留意到一些古怪的URL,比如“http://host/path?user=Marty+Hall&origin=bwi&dest=lax”。這個(gè)URL中位于問號(hào)后面的部分,即“user=Marty+Hall&origin=bwi&dest=lax”,就是表單數(shù)據(jù),這是將Web頁面數(shù)據(jù)發(fā)送給服務(wù)器程序的最常用方法。對(duì)于GET請(qǐng)求,表單數(shù)據(jù)附加到URL的問號(hào)后面(如上例所示);對(duì)于POST請(qǐng)求,表單數(shù)據(jù)用一個(gè)單獨(dú)的行發(fā)送給服務(wù)器。
??
? 以前,從這種形式的數(shù)據(jù)提取出所需要的表單變量是CGI編程中最麻煩的事情之一。首先,GET請(qǐng)求和POST請(qǐng)求的數(shù)據(jù)提取方法不同:對(duì)于GET請(qǐng)求,通常要通過QUERY_STRING環(huán)境變量提取數(shù)據(jù);對(duì)于POST請(qǐng)求,則一般通過標(biāo)準(zhǔn)輸入提取數(shù)據(jù)。第二,程序員必須負(fù)責(zé)在“&”符號(hào)處截?cái)嘧兞棵?變量值對(duì),再分離出變量名字(等號(hào)左邊)和變量值(等號(hào)右邊)。第三,必須對(duì)變量值進(jìn)行URL反編碼操作。因?yàn)榘l(fā)送數(shù)據(jù)的時(shí)候,字母和數(shù)字以原來的形式發(fā)送,但空格被轉(zhuǎn)換成加號(hào),其他字符被轉(zhuǎn)換成“%XX”形式,其中XX是十六進(jìn)制表示的字符ASCII(或者ISO Latin-1)編碼值。例如,如果HTML表單中名為“users”的域值為“~hall, ~gates, and ~mcnealy”,則實(shí)際向服務(wù)器發(fā)送的數(shù)據(jù)為“users=%7Ehall%2C+%7Egates%2C+and+%7Emcnealy”。最后,即第四個(gè)導(dǎo)致解析表單數(shù)據(jù)非常困難的原因在于,變量值既可能被省略(如“param1=val1&param2=&param3=val3”),也有可能一個(gè)變量擁有一個(gè)以上的值,即同一個(gè)變量可能出現(xiàn)一次以上(如“param1=val1&param2=val2&param1=val3”)。
??
? Java Servlet的好處之一就在于所有上述解析操作都能夠自動(dòng)完成。只需要簡(jiǎn)單地調(diào)用一下HttpServletRequest的getParameter方法、在調(diào)用參數(shù)中提供表單變量的名字(大小寫敏感)即可,而且GET請(qǐng)求和POST請(qǐng)求的處理方法完全相同。
??
? getParameter方法的返回值是一個(gè)字符串,它是參數(shù)中指定的變量名字第一次出現(xiàn)所對(duì)應(yīng)的值經(jīng)反編碼得到得字符串(可以直接使用)。如果指定的表單變量存在,但沒有值,getParameter返回空字符串;如果指定的表單變量不存在,則返回null。如果表單變量可能對(duì)應(yīng)多個(gè)值,可以用getParameterValues來取代getParameter。getParameterValues能夠返回一個(gè)字符串?dāng)?shù)組。
??
? 最后,雖然在實(shí)際應(yīng)用中Servlet很可能只會(huì)用到那些已知名字的表單變量,但在調(diào)試環(huán)境中,獲得完整的表單變量名字列表往往是很有用的,利用getParamerterNames方法可以方便地實(shí)現(xiàn)這一點(diǎn)。getParamerterNames返回的是一個(gè)Enumeration,其中的每一項(xiàng)都可以轉(zhuǎn)換為調(diào)用getParameter的字符串。
??
? 4.2 實(shí)例:讀取三個(gè)表單變量
??
? 下面是一個(gè)簡(jiǎn)單的例子,它讀取三個(gè)表單變量param1、param2和param3,并以HTML列表的形式列出它們的值。請(qǐng)注意,雖然在發(fā)送應(yīng)答內(nèi)容之前必須指定應(yīng)答類型(包括內(nèi)容類型、狀態(tài)以及其他HTTP頭信息),但Servlet對(duì)何時(shí)讀取請(qǐng)求內(nèi)容卻沒有什么要求。
??
? 另外,我們也可以很容易地把Servlet做成既能處理GET請(qǐng)求,也能夠處理POST請(qǐng)求,這只需要在doPost方法中調(diào)用doGet方法,或者覆蓋service方法(service方法調(diào)用doGet、doPost、doHead等方法)。在實(shí)際編程中這是一種標(biāo)準(zhǔn)的方法,因?yàn)樗恍枰苌俚念~外工作,卻能夠增加客戶端編碼的靈活性。
??
? 如果你習(xí)慣用傳統(tǒng)的CGI方法,通過標(biāo)準(zhǔn)輸入讀取POST數(shù)據(jù),那么在Servlet中也有類似的方法,即在HttpServletRequest上調(diào)用getReader或者getInputStream,但這種方法對(duì)普通的表單變量來說太麻煩。然而,如果是要上載文件,或者POST數(shù)據(jù)是通過專門的客戶程序而不是HTML表單發(fā)送,那么就要用到這種方法。
??
? 注意用第二種方法讀取POST數(shù)據(jù)時(shí),不能再用getParameter來讀取這些數(shù)據(jù)。
??
? 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 = "讀取三個(gè)請(qǐng)求參數(shù)";
?? 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 實(shí)例:輸出所有的表單數(shù)據(jù)
??
? 下面這個(gè)例子尋找表單所發(fā)送的所有變量名字,并把它們放入表格中,沒有值或者有多個(gè)值的變量都突出顯示。
??
? 首先,程序通過HttpServletRequest的getParameterNames方法得到所有的變量名字,getParameterNames返回的是一個(gè)Enumeration。接下來,程序用循環(huán)遍歷這個(gè)Enumeration,通過hasMoreElements確定何時(shí)結(jié)束循環(huán),利用nextElement得到Enumeration中的各個(gè)項(xiàng)。由于nextElement返回的是一個(gè)Object,程序把它轉(zhuǎn)換成字符串后再用這個(gè)字符串來調(diào)用getParameterValues。
??
? getParameterValues返回一個(gè)字符串?dāng)?shù)組,如果這個(gè)數(shù)組只有一個(gè)元素且等于空字符串,說明這個(gè)表單變量沒有值,Servlet以斜體形式輸出“No Value”;如果數(shù)組元素個(gè)數(shù)大于1,說明這個(gè)表單變量有多個(gè)值,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 = "讀取所有請(qǐng)求參數(shù)";
?? 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>參數(shù)名字<TH>參數(shù)值");
?? 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);
?? }
? }
??
??
??
??
? 測(cè)試表單
??
? 下面是向上述Servlet發(fā)送數(shù)據(jù)的表單PostForm.html。就像所有包含密碼輸入域的表單一樣,該表單用POST方法發(fā)送數(shù)據(jù)。我們可以看到,在Servlet中同時(shí)實(shí)現(xiàn)doGet和doPost這兩種方法為表單制作帶來了方便。
? <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
? <HTML>
? <HEAD>
?? <TITLE>示例表單</TITLE>
? </HEAD>
??
? <BODY BGCOLOR="#FDF5E6">
? <H1 ALIGN="CENTER">用POST方法發(fā)送數(shù)據(jù)的表單</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>
5.1 HTTP請(qǐng)求頭概述
??
? HTTP客戶程序(例如瀏覽器),向服務(wù)器發(fā)送請(qǐng)求的時(shí)候必須指明請(qǐng)求類型(一般是GET或者POST)。如有必要,客戶程序還可以選擇發(fā)送其他的請(qǐng)求頭。大多數(shù)請(qǐng)求頭并不是必需的,但Content-Length除外。對(duì)于POST請(qǐng)求來說Content-Length必須出現(xiàn)。
??
? 下面是一些最常見的請(qǐng)求頭:
??
? Accept:瀏覽器可接受的MIME類型。
? Accept-Charset:瀏覽器可接受的字符集。
? Accept-Encoding:瀏覽器能夠進(jìn)行解碼的數(shù)據(jù)編碼方式,比如gzip。Servlet能夠向支持gzip的瀏覽器返回經(jīng)gzip編碼的HTML頁面。許多情形下這可以減少5到10倍的下載時(shí)間。
? Accept-Language:瀏覽器所希望的語言種類,當(dāng)服務(wù)器能夠提供一種以上的語言版本時(shí)要用到。
? Authorization:授權(quán)信息,通常出現(xiàn)在對(duì)服務(wù)器發(fā)送的WWW-Authenticate頭的應(yīng)答中。
? Connection:表示是否需要持久連接。如果Servlet看到這里的值為“Keep-Alive”,或者看到請(qǐng)求使用的是HTTP 1.1(HTTP 1.1默認(rèn)進(jìn)行持久連接),它就可以利用持久連接的優(yōu)點(diǎn),當(dāng)頁面包含多個(gè)元素時(shí)(例如Applet,圖片),顯著地減少下載所需要的時(shí)間。要實(shí)現(xiàn)這一點(diǎn),Servlet需要在應(yīng)答中發(fā)送一個(gè)Content-Length頭,最簡(jiǎn)單的實(shí)現(xiàn)方法是:先把內(nèi)容寫入ByteArrayOutputStream,然后在正式寫出內(nèi)容之前計(jì)算它的大小。
? Content-Length:表示請(qǐng)求消息正文的長(zhǎng)度。
? Cookie:這是最重要的請(qǐng)求頭信息之一,參見后面《Cookie處理》一章中的討論。
? From:請(qǐng)求發(fā)送者的email地址,由一些特殊的Web客戶程序使用,瀏覽器不會(huì)用到它。
? Host:初始URL中的主機(jī)和端口。
? If-Modified-Since:只有當(dāng)所請(qǐng)求的內(nèi)容在指定的日期之后又經(jīng)過修改才返回它,否則返回304“Not Modified”應(yīng)答。
? Pragma:指定“no-cache”值表示服務(wù)器必須返回一個(gè)刷新后的文檔,即使它是代理服務(wù)器而且已經(jīng)有了頁面的本地拷貝。
? Referer:包含一個(gè)URL,用戶從該URL代表的頁面出發(fā)訪問當(dāng)前請(qǐng)求的頁面。
? User-Agent:瀏覽器類型,如果Servlet返回的內(nèi)容與瀏覽器類型有關(guān)則該值非常有用。
? UA-Pixels,UA-Color,UA-OS,UA-CPU:由某些版本的IE瀏覽器所發(fā)送的非標(biāo)準(zhǔn)的請(qǐng)求頭,表示屏幕大小、顏色深度、操作系統(tǒng)和CPU類型。
? 有關(guān)HTTP頭完整、詳細(xì)的說明,請(qǐng)參見
http://www.w3.org/Protocols/ 的HTTP規(guī)范。
??
? 5.2 在Servlet中讀取請(qǐng)求頭
??
? 在Servlet中讀取HTTP頭是非常方便的,只需要調(diào)用一下HttpServletRequest的getHeader方法即可。如果客戶請(qǐng)求中提供了指定的頭信息,getHeader返回對(duì)應(yīng)的字符串;否則,返回null。部分頭信息經(jīng)常要用到,它們有專用的訪問方法:getCookies方法返回Cookie頭的內(nèi)容,經(jīng)解析后存放在Cookie對(duì)象的數(shù)組中,請(qǐng)參見后面有關(guān)Cookie章節(jié)的討論;getAuthType和getRemoteUser方法分別讀取Authorization頭中的一部分內(nèi)容;getDateHeader和getIntHeader方法讀取指定的頭,然后返回日期值或整數(shù)值。
??
? 除了讀取指定的頭之外,利用getHeaderNames還可以得到請(qǐng)求中所有頭名字的一個(gè)Enumeration對(duì)象。
??
? 最后,除了查看請(qǐng)求頭信息之外,我們還可以從請(qǐng)求主命令行獲得一些信息。getMethod方法返回請(qǐng)求方法,請(qǐng)求方法通常是GET或者POST,但也有可能是HEAD、PUT或者DELETE。getRequestURI方法返回URI(URI是URL的從主機(jī)和端口之后到表單數(shù)據(jù)之前的那一部分)。getRequestProtocol返回請(qǐng)求命令的第三部分,一般是“HTTP/1.0”或者“HTTP/1.1”。
??
? 5.3 實(shí)例:輸出所有的請(qǐng)求頭
??
? 下面的Servlet實(shí)例把所有接收到的請(qǐng)求頭和它的值以表格的形式輸出。另外,該Servlet還會(huì)輸出主請(qǐng)求命令的三個(gè)部分:請(qǐng)求方法,URI,協(xié)議/版本。
??
? 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 = "顯示所有請(qǐng)求頭";
?? 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);
?? }
? }
6.1 CGI變量概述
??
? 如果你是從傳統(tǒng)的CGI編程轉(zhuǎn)而學(xué)習(xí)Java Servlet,或許已經(jīng)習(xí)慣了“CGI變量”這一概念。CGI變量匯集了各種有關(guān)請(qǐng)求的信息:
??
? 部分來自HTTP請(qǐng)求命令和請(qǐng)求頭,例如Content-Length頭;
? 部分來自Socket本身,例如主機(jī)的名字和IP地址;
? 也有部分與服務(wù)器安裝配置有關(guān),例如URL到實(shí)際路徑的映射。
? 6.2 標(biāo)準(zhǔn)CGI變量的Servlet等價(jià)表示
??
? 下表假定request對(duì)象是提供給doGet和doPost方法的HttpServletRequest類型對(duì)象。 CGI變量 含義 從doGet或doPost訪問
? AUTH_TYPE 如果提供了Authorization頭,這里指定了具體的模式(basic或者digest)。 request.getAuthType()
? CONTENT_LENGTH 只用于POST請(qǐng)求,表示所發(fā)送數(shù)據(jù)的字節(jié)數(shù)。 嚴(yán)格地講,等價(jià)的表達(dá)方式應(yīng)該是String.valueOf(request.getContentLength())(返回一個(gè)字符串)。但更常見的是用request.getContentLength()返回含義相同的整數(shù)。
? CONTENT_TYPE 如果指定的話,表示后面所跟數(shù)據(jù)的類型。 request.getContentType()
? DOCUMENT_ROOT 與
http://host/對(duì)應(yīng)的路徑。 getServletContext().getRealPath("/")
? 注意低版本Servlet規(guī)范中的等價(jià)表達(dá)方式是request.getRealPath("/")。
??
? HTTP_XXX_YYY 訪問任意HTTP頭。 request.getHeader("Xxx-Yyy")
? PATH_INFO URL中的附加路徑信息,即URL中Servlet路徑之后、查詢字符串之前的那部分。 request.getPathInfo()
? PATH_TRANSLATED 映射到服務(wù)器實(shí)際路徑之后的路徑信息。 request.getPathTranslated()
? QUERY_STRING 這是字符串形式的附加到URL后面的查詢字符串,數(shù)據(jù)仍舊是URL編碼的。在Servlet中很少需要用到未經(jīng)解碼的數(shù)據(jù),一般使用getParameter訪問各個(gè)參數(shù)。 request.getQueryString()
? REMOTE_ADDR 發(fā)出請(qǐng)求的客戶機(jī)的IP地址。 request.getRemoteAddr()
? REMOTE_HOST 發(fā)出請(qǐng)求的客戶機(jī)的完整的域名,如java.sun.com。如果不能確定該域名,則返回IP地址。 request.getRemoteHost()
? REMOTE_USER 如果提供了Authorization頭,則代表其用戶部分。它代表發(fā)出請(qǐng)求的用戶的名字。 request.getRemoteUser()
? REQUEST_METHOD 請(qǐng)求類型。通常是GET或者POST。但偶爾也會(huì)出現(xiàn)HEAD,PUT, DELETE,OPTIONS,或者 TRACE. request.getMethod()
? SCRIPT_NAME URL中調(diào)用Servlet的那一部分,不包含附加路徑信息和查詢字符串。 request.getServletPath()
? SERVER_NAME Web服務(wù)器名字。 request.getServerName()
? SERVER_PORT 服務(wù)器監(jiān)聽的端口。 嚴(yán)格地說,等價(jià)表達(dá)應(yīng)該是返回字符串的String.valueOf(request.getServerPort())。但經(jīng)常使用返回整數(shù)值的request.getServerPort()。
? SERVER_PROTOCOL 請(qǐng)求命令中的協(xié)議名字和版本(即HTTP/1.0或HTTP/1.1)。 request.getProtocol()
? SERVER_SOFTWARE Servlet引擎的名字和版本。 getServletContext().getServerInfo()
??
??
? 6.3 實(shí)例:讀取CGI變量
??
? 下面這個(gè)Servlet創(chuàng)建一個(gè)表格,顯示除了HTTP_XXX_YYY之外的所有CGI變量。HTTP_XXX_YYY是HTTP請(qǐng)求頭信息,請(qǐng)參見上一節(jié)介紹。
??
? 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);
?? }
? }
7.1 狀態(tài)代碼概述
??
? Web服務(wù)器響應(yīng)瀏覽器或其他客戶程序的請(qǐng)求時(shí),其應(yīng)答一般由以下幾個(gè)部分組成:一個(gè)狀態(tài)行,幾個(gè)應(yīng)答頭,一個(gè)空行,內(nèi)容文檔。下面是一個(gè)最簡(jiǎn)單的應(yīng)答:
? HTTP/1.1 200 OK
? Content-Type: text/plain
??
? Hello World
??
??
??
??
? 狀態(tài)行包含HTTP版本、狀態(tài)代碼、與狀態(tài)代碼對(duì)應(yīng)的簡(jiǎn)短說明信息。在大多數(shù)情況下,除了Content-Type之外的所有應(yīng)答頭都是可選的。但Content-Type是必需的,它描述的是后面文檔的MIME類型。雖然大多數(shù)應(yīng)答都包含一個(gè)文檔,但也有一些不包含,例如對(duì)HEAD請(qǐng)求的應(yīng)答永遠(yuǎn)不會(huì)附帶文檔。有許多狀態(tài)代碼實(shí)際上用來標(biāo)識(shí)一次失敗的請(qǐng)求,這些應(yīng)答也不包含文檔(或只包含一個(gè)簡(jiǎn)短的錯(cuò)誤信息說明)。
??
? Servlet可以利用狀態(tài)代碼來實(shí)現(xiàn)許多功能。例如,可以把用戶重定向到另一個(gè)網(wǎng)站;可以指示出后面的文檔是圖片、PDF文件或HTML文件;可以告訴用戶必須提供密碼才能訪問文檔;等等。這一部分我們將具體討論各種狀態(tài)代碼的含義以及利用這些代碼可以做些什么。
??
? 7.2 設(shè)置狀態(tài)代碼
??
? 如前所述,HTTP應(yīng)答狀態(tài)行包含HTTP版本、狀態(tài)代碼和對(duì)應(yīng)的狀態(tài)信息。由于狀態(tài)信息直接和狀態(tài)代碼相關(guān),而HTTP版本又由服務(wù)器確定,因此需要Servlet設(shè)置的只有一個(gè)狀態(tài)代碼。
??
? Servlet設(shè)置狀態(tài)代碼一般使用HttpServletResponse的setStatus方法。setStatus方法的參數(shù)是一個(gè)整數(shù)(即狀態(tài)代碼),不過為了使得代碼具有更好的可讀性,可以用HttpServletResponse中定義的常量來避免直接使用整數(shù)。這些常量根據(jù)HTTP 1.1中的標(biāo)準(zhǔn)狀態(tài)信息命名,所有的名字都加上了SC前綴(Status Code的縮寫)并大寫,同時(shí)把空格轉(zhuǎn)換成了下劃線。也就是說,與狀態(tài)代碼404對(duì)應(yīng)的狀態(tài)信息是“Not Found”,則HttpServletResponse中的對(duì)應(yīng)常量名字為SC_NOT_FOUND。但有兩個(gè)例外:和狀態(tài)代碼302對(duì)應(yīng)的常量根據(jù)HTTP 1.0命名,而307沒有對(duì)應(yīng)的常量。
??
? 設(shè)置狀態(tài)代碼并非總是意味著不要再返回文檔。例如,雖然大多數(shù)服務(wù)器返回404應(yīng)答時(shí)會(huì)輸出簡(jiǎn)單的“File Not Found”信息,但Servlet也可以定制這個(gè)應(yīng)答。不過,定制應(yīng)答時(shí)應(yīng)當(dāng)在通過PrintWriter發(fā)送任何內(nèi)容之前先調(diào)用response.setStatus。
??
? 雖然設(shè)置狀態(tài)代碼一般使用的是response.setStauts(int)方法,但為了簡(jiǎn)單起見,HttpServletResponse為兩種常見的情形提供了專用方法:sendError方法生成一個(gè)404應(yīng)答,同時(shí)生成一個(gè)簡(jiǎn)短的HTML錯(cuò)誤信息文檔;sendRedirect方法生成一個(gè)302應(yīng)答,同時(shí)在Location頭中指示新文檔的URL。
??
? 7.3 HTTP 1.1狀態(tài)代碼及其含義
??
? 下表顯示了常見的HTTP 1.1狀態(tài)代碼以及它們對(duì)應(yīng)的狀態(tài)信息和含義。
??
? 應(yīng)當(dāng)謹(jǐn)慎地使用那些只有HTTP 1.1支持的狀態(tài)代碼,因?yàn)樵S多瀏覽器還只能夠支持HTTP 1.0。如果你使用了HTTP 1.1特有的狀態(tài)代碼,最好能夠檢查一下請(qǐng)求的HTTP版本號(hào)(通過HttpServletRequest的getProtocol方法)。 狀態(tài)代碼 狀態(tài)信息 含義
? 100 Continue 初始的請(qǐng)求已經(jīng)接受,客戶應(yīng)當(dāng)繼續(xù)發(fā)送請(qǐng)求的其余部分。(HTTP 1.1新)
? 101 Switching Protocols 服務(wù)器將遵從客戶的請(qǐng)求轉(zhuǎn)換到另外一種協(xié)議(HTTP 1.1新)
? 200 OK 一切正常,對(duì)GET和POST請(qǐng)求的應(yīng)答文檔跟在后面。如果不用setStatus設(shè)置狀態(tài)代碼,Servlet默認(rèn)使用202狀態(tài)代碼。
? 201 Created 服務(wù)器已經(jīng)創(chuàng)建了文檔,Location頭給出了它的URL。
? 202 Accepted 已經(jīng)接受請(qǐng)求,但處理尚未完成。
? 203 Non-Authoritative Information 文檔已經(jīng)正常地返回,但一些應(yīng)答頭可能不正確,因?yàn)槭褂玫氖俏臋n的拷貝(HTTP 1.1新)。
? 204 No Content 沒有新文檔,瀏覽器應(yīng)該繼續(xù)顯示原來的文檔。如果用戶定期地刷新頁面,而Servlet可以確定用戶文檔足夠新,這個(gè)狀態(tài)代碼是很有用的。
? 205 Reset Content 沒有新的內(nèi)容,但瀏覽器應(yīng)該重置它所顯示的內(nèi)容。用來強(qiáng)制瀏覽器清除表單輸入內(nèi)容(HTTP 1.1新)。
? 206 Partial Content 客戶發(fā)送了一個(gè)帶有Range頭的GET請(qǐng)求,服務(wù)器完成了它(HTTP 1.1新)。
? 300 Multiple Choices 客戶請(qǐng)求的文檔可以在多個(gè)位置找到,這些位置已經(jīng)在返回的文檔內(nèi)列出。如果服務(wù)器要提出優(yōu)先選擇,則應(yīng)該在Location應(yīng)答頭指明。
? 301 Moved Permanently 客戶請(qǐng)求的文檔在其他地方,新的URL在Location頭中給出,瀏覽器應(yīng)該自動(dòng)地訪問新的URL。
? 302 Found 類似于301,但新的URL應(yīng)該被視為臨時(shí)性的替代,而不是永久性的。注意,在HTTP1.0中對(duì)應(yīng)的狀態(tài)信息是“Moved Temporatily”,而HttpServletResponse中相應(yīng)的常量是SC_MOVED_TEMPORARILY,而不是SC_FOUND。
? 出現(xiàn)該狀態(tài)代碼時(shí),瀏覽器能夠自動(dòng)訪問新的URL,因此它是一個(gè)很有用的狀態(tài)代碼。為此,Servlet提供了一個(gè)專用的方法,即sendRedirect。使用response.sendRedirect(url)比使用response.setStatus(response.SC_MOVED_TEMPORARILY)和response.setHeader("Location",url)更好。這是因?yàn)椋?
??
? 首先,代碼更加簡(jiǎn)潔。
? 第二,使用sendRedirect,Servlet會(huì)自動(dòng)構(gòu)造一個(gè)包含新鏈接的頁面(用于那些不能自動(dòng)重定向的老式瀏覽器)。
? 最后,sendRedirect能夠處理相對(duì)URL,自動(dòng)把它們轉(zhuǎn)換成絕對(duì)URL。
? 注意這個(gè)狀態(tài)代碼有時(shí)候可以和301替換使用。例如,如果瀏覽器錯(cuò)誤地請(qǐng)求
http://host/~user(缺少了后面的斜杠),有的服務(wù)器返回301,有的則返回302。
??
? 嚴(yán)格地說,我們只能假定只有當(dāng)原來的請(qǐng)求是GET時(shí)瀏覽器才會(huì)自動(dòng)重定向。請(qǐng)參見307。
??
? 303 See Other 類似于301/302,不同之處在于,如果原來的請(qǐng)求是POST,Location頭指定的重定向目標(biāo)文檔應(yīng)該通過GET提?。℉TTP 1.1新)。
? 304 Not Modified 客戶端有緩沖的文檔并發(fā)出了一個(gè)條件性的請(qǐng)求(一般是提供If-Modified-Since頭表示客戶只想比指定日期更新的文檔)。服務(wù)器告訴客戶,原來緩沖的文檔還可以繼續(xù)使用。
? 305 Use Proxy 客戶請(qǐng)求的文檔應(yīng)該通過Location頭所指明的代理服務(wù)器提?。℉TTP 1.1新)。
? 307 Temporary Redirect 和302(Found)相同。許多瀏覽器會(huì)錯(cuò)誤地響應(yīng)302應(yīng)答進(jìn)行重定向,即使原來的請(qǐng)求是POST,即使它實(shí)際上只能在POST請(qǐng)求的應(yīng)答是303時(shí)才能重定向。由于這個(gè)原因,HTTP 1.1新增了307,以便更加清除地區(qū)分幾個(gè)狀態(tài)代碼:當(dāng)出現(xiàn)303應(yīng)答時(shí),瀏覽器可以跟隨重定向的GET和POST請(qǐng)求;如果是307應(yīng)答,則瀏覽器只能跟隨對(duì)GET請(qǐng)求的重定向。
? 注意,HttpServletResponse中沒有為該狀態(tài)代碼提供相應(yīng)的常量。(HTTP 1.1新)
??
? 400 Bad Request 請(qǐng)求出現(xiàn)語法錯(cuò)誤。
? 401 Unauthorized 客戶試圖未經(jīng)授權(quán)訪問受密碼保護(hù)的頁面。應(yīng)答中會(huì)包含一個(gè)WWW-Authenticate頭,瀏覽器據(jù)此顯示用戶名字/密碼對(duì)話框,然后在填寫合適的Authorization頭后再次發(fā)出請(qǐng)求。
? 403 Forbidden 資源不可用。服務(wù)器理解客戶的請(qǐng)求,但拒絕處理它。通常由于服務(wù)器上文件或目錄的權(quán)限設(shè)置導(dǎo)致。
? 404 Not Found 無法找到指定位置的資源。這也是一個(gè)常用的應(yīng)答,HttpServletResponse專門提供了相應(yīng)的方法:sendError(message)。
? 405 Method Not Allowed 請(qǐng)求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)對(duì)指定的資源不適用。(HTTP 1.1新)
? 406 Not Acceptable 指定的資源已經(jīng)找到,但它的MIME類型和客戶在Accpet頭中所指定的不兼容(HTTP 1.1新)。
? 407 Proxy Authentication Required 類似于401,表示客戶必須先經(jīng)過代理服務(wù)器的授權(quán)。(HTTP 1.1新)
? 408 Request Timeout 在服務(wù)器許可的等待時(shí)間內(nèi),客戶一直沒有發(fā)出任何請(qǐng)求。客戶可以在以后重復(fù)同一請(qǐng)求。(HTTP 1.1新)
? 409 Conflict 通常和PUT請(qǐng)求有關(guān)。由于請(qǐng)求和資源的當(dāng)前狀態(tài)相沖突,因此請(qǐng)求不能成功。(HTTP 1.1新)
? 410 Gone 所請(qǐng)求的文檔已經(jīng)不再可用,而且服務(wù)器不知道應(yīng)該重定向到哪一個(gè)地址。它和404的不同在于,返回407表示文檔永久地離開了指定的位置,而404表示由于未知的原因文檔不可用。(HTTP 1.1新)
? 411 Length Required 服務(wù)器不能處理請(qǐng)求,除非客戶發(fā)送一個(gè)Content-Length頭。(HTTP 1.1新)
? 412 Precondition Failed 請(qǐng)求頭中指定的一些前提條件失敗(HTTP 1.1新)。
? 413 Request Entity Too Large 目標(biāo)文檔的大小超過服務(wù)器當(dāng)前愿意處理的大小。如果服務(wù)器認(rèn)為自己能夠稍后再處理該請(qǐng)求,則應(yīng)該提供一個(gè)Retry-After頭(HTTP 1.1新)。
? 414 Request URI Too Long URI太長(zhǎng)(HTTP 1.1新)。
? 416 Requested Range Not Satisfiable 服務(wù)器不能滿足客戶在請(qǐng)求中指定的Range頭。(HTTP 1.1新)
? 500 Internal Server Error 服務(wù)器遇到了意料不到的情況,不能完成客戶的請(qǐng)求。
? 501 Not Implemented 服務(wù)器不支持實(shí)現(xiàn)請(qǐng)求所需要的功能。例如,客戶發(fā)出了一個(gè)服務(wù)器不支持的PUT請(qǐng)求。
? 502 Bad Gateway 服務(wù)器作為網(wǎng)關(guān)或者代理時(shí),為了完成請(qǐng)求訪問下一個(gè)服務(wù)器,但該服務(wù)器返回了非法的應(yīng)答。
? 503 Service Unavailable 服務(wù)器由于維護(hù)或者負(fù)載過重未能應(yīng)答。例如,Servlet可能在數(shù)據(jù)庫連接池已滿的情況下返回503。服務(wù)器返回503時(shí)可以提供一個(gè)Retry-After頭。
? 504 Gateway Timeout 由作為代理或網(wǎng)關(guān)的服務(wù)器使用,表示不能及時(shí)地從遠(yuǎn)程服務(wù)器獲得應(yīng)答。(HTTP 1.1新)
? 505 HTTP Version Not Supported 服務(wù)器不支持請(qǐng)求中所指明的HTTP版本。(HTTP 1.1新)
??
??
? 7.4 實(shí)例:訪問多個(gè)搜索引擎
??
? 下面這個(gè)例子用到了除200之外的另外兩個(gè)常見狀態(tài)代碼:302和404。302通過sendRedirect方法設(shè)置,404通過sendError方法設(shè)置。
??
? 在這個(gè)例子中,首先出現(xiàn)的HTML表單用來選擇搜索引擎、搜索字符串、每頁顯示的搜索結(jié)果數(shù)量。表單提交后,Servlet提取這三個(gè)變量,按照所選擇的搜索引擎的要求構(gòu)造出包含這些變量的URL,然后把用戶重定向到這個(gè)URL。如果用戶不能正確地選擇搜索引擎,或者利用其他表單發(fā)送了一個(gè)不認(rèn)識(shí)的搜索引擎名字,則返回一個(gè)提示搜索引擎找不到的404頁面。
??
? SearchEngines.java
??
? 注意:這個(gè)Servlet要用到后面給出的SearchSpec類,SearchSpec的功能是構(gòu)造適合不同搜索引擎的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自動(dòng)解碼URL編碼的查詢字符串。由于我們
?? // 要把查詢字符串發(fā)送給另一個(gè)服務(wù)器,因此再次使用
?? // URLEncoder進(jìn)行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
??
? 下面是調(diào)用上述Servlet的HTML表單。
? <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
? <HTML>
? <HEAD>
?? <TITLE>訪問多個(gè)搜索引擎</TITLE>
? </HEAD>
??
? <BODY BGCOLOR="#FDF5E6">
??
? <FORM ACTION="/servlet/hall.SearchEngines">
?? <CENTER>
?? 搜索關(guān)鍵字:
?? <INPUT TYPE="TEXT" NAME="searchString"><BR>
?? 每頁顯示幾個(gè)查詢結(jié)果:
?? <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>
8.1 HTTP應(yīng)答頭概述
??
? Web服務(wù)器的HTTP應(yīng)答一般由以下幾項(xiàng)構(gòu)成:一個(gè)狀態(tài)行,一個(gè)或多個(gè)應(yīng)答頭,一個(gè)空行,內(nèi)容文檔。設(shè)置HTTP應(yīng)答頭往往和設(shè)置狀態(tài)行中的狀態(tài)代碼結(jié)合起來。例如,有好幾個(gè)表示“文檔位置已經(jīng)改變”的狀態(tài)代碼都伴隨著一個(gè)Location頭,而401(Unauthorized)狀態(tài)代碼則必須伴隨一個(gè)WWW-Authenticate頭。
??
? 然而,即使在沒有設(shè)置特殊含義的狀態(tài)代碼時(shí),指定應(yīng)答頭也是很有用的。應(yīng)答頭可以用來完成:設(shè)置Cookie,指定修改日期,指示瀏覽器按照指定的間隔刷新頁面,聲明文檔的長(zhǎng)度以便利用持久HTTP連接,……等等許多其他任務(wù)。
??
? 設(shè)置應(yīng)答頭最常用的方法是HttpServletResponse的setHeader,該方法有兩個(gè)參數(shù),分別表示應(yīng)答頭的名字和值。和設(shè)置狀態(tài)代碼相似,設(shè)置應(yīng)答頭應(yīng)該在發(fā)送任何文檔內(nèi)容之前進(jìn)行。
??
? setDateHeader方法和setIntHeadr方法專門用來設(shè)置包含日期和整數(shù)值的應(yīng)答頭,前者避免了把Java時(shí)間轉(zhuǎn)換為GMT時(shí)間字符串的麻煩,后者則避免了把整數(shù)轉(zhuǎn)換為字符串的麻煩。
??
? HttpServletResponse還提供了許多設(shè)置常見應(yīng)答頭的簡(jiǎn)便方法,如下所示:
??
? setContentType:設(shè)置Content-Type頭。大多數(shù)Servlet都要用到這個(gè)方法。
? setContentLength:設(shè)置Content-Length頭。對(duì)于支持持久HTTP連接的瀏覽器來說,這個(gè)函數(shù)是很有用的。
? addCookie:設(shè)置一個(gè)Cookie(Servlet API中沒有setCookie方法,因?yàn)閼?yīng)答往往包含多個(gè)Set-Cookie頭)。
? 另外,如上節(jié)介紹,sendRedirect方法設(shè)置狀態(tài)代碼302時(shí)也會(huì)設(shè)置Location頭。
? 8.2 常見應(yīng)答頭及其含義
??
? 有關(guān)HTTP頭詳細(xì)和完整的說明,請(qǐng)參見
http://www.w3.org/Protocols/ 規(guī)范。
??
? 應(yīng)答頭 說明
? Allow 服務(wù)器支持哪些請(qǐng)求方法(如GET、POST等)。
? Content-Encoding 文檔的編碼(Encode)方法。只有在解碼之后才可以得到Content-Type頭指定的內(nèi)容類型。利用gzip壓縮文檔能夠顯著地減少HTML文檔的下載時(shí)間。Java的GZIPOutputStream可以很方便地進(jìn)行g(shù)zip壓縮,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此,Servlet應(yīng)該通過查看Accept-Encoding頭(即request.getHeader("Accept-Encoding"))檢查瀏覽器是否支持gzip,為支持gzip的瀏覽器返回經(jīng)gzip壓縮的HTML頁面,為其他瀏覽器返回普通頁面。
? Content-Length 表示內(nèi)容長(zhǎng)度。只有當(dāng)瀏覽器使用持久HTTP連接時(shí)才需要這個(gè)數(shù)據(jù)。如果你想要利用持久連接的優(yōu)勢(shì),可以把輸出文檔寫入ByteArrayOutputStram,完成后查看其大小,然后把該值放入Content-Length頭,最后通過byteArrayStream.writeTo(response.getOutputStream()發(fā)送內(nèi)容。
? Content-Type 表示后面的文檔屬于什么MIME類型。Servlet默認(rèn)為text/plain,但通常需要顯式地指定為text/html。由于經(jīng)常要設(shè)置Content-Type,因此HttpServletResponse提供了一個(gè)專用的方法setContentTyep。
? Date 當(dāng)前的GMT時(shí)間。你可以用setDateHeader來設(shè)置這個(gè)頭以避免轉(zhuǎn)換時(shí)間格式的麻煩。
? Expires 應(yīng)該在什么時(shí)候認(rèn)為文檔已經(jīng)過期,從而不再緩存它?
? Last-Modified 文檔的最后改動(dòng)時(shí)間??蛻艨梢酝ㄟ^If-Modified-Since請(qǐng)求頭提供一個(gè)日期,該請(qǐng)求將被視為一個(gè)條件GET,只有改動(dòng)時(shí)間遲于指定時(shí)間的文檔才會(huì)返回,否則返回一個(gè)304(Not Modified)狀態(tài)。Last-Modified也可用setDateHeader方法來設(shè)置。
? Location 表示客戶應(yīng)當(dāng)?shù)侥睦锶ヌ崛∥臋n。Location通常不是直接設(shè)置的,而是通過HttpServletResponse的sendRedirect方法,該方法同時(shí)設(shè)置狀態(tài)代碼為302。
? Refresh 表示瀏覽器應(yīng)該在多少時(shí)間之后刷新文檔,以秒計(jì)。除了刷新當(dāng)前文檔之外,你還可以通過setHeader("Refresh", "5; URL=http://host/path")讓瀏覽器讀取指定的頁面。
? 注意這種功能通常是通過設(shè)置HTML頁面HEAD區(qū)的<META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path">實(shí)現(xiàn),這是因?yàn)?,自?dòng)刷新或重定向?qū)τ谀切┎荒苁褂肅GI或Servlet的HTML編寫者十分重要。但是,對(duì)于Servlet來說,直接設(shè)置Refresh頭更加方便。
??
? 注意Refresh的意義是“N秒之后刷新本頁面或訪問指定頁面”,而不是“每隔N秒刷新本頁面或訪問指定頁面”。因此,連續(xù)刷新要求每次都發(fā)送一個(gè)Refresh頭,而發(fā)送204狀態(tài)代碼則可以阻止瀏覽器繼續(xù)刷新,不管是使用Refresh頭還是<META HTTP-EQUIV="Refresh" ...>。
??
? 注意Refresh頭不屬于HTTP 1.1正式規(guī)范的一部分,而是一個(gè)擴(kuò)展,但Netscape和IE都支持它。
??
? Server 服務(wù)器名字。Servlet一般不設(shè)置這個(gè)值,而是由Web服務(wù)器自己設(shè)置。
? Set-Cookie 設(shè)置和頁面關(guān)聯(lián)的Cookie。Servlet不應(yīng)使用response.setHeader("Set-Cookie", ...),而是應(yīng)使用HttpServletResponse提供的專用方法addCookie。參見下文有關(guān)Cookie設(shè)置的討論。
? WWW-Authenticate 客戶應(yīng)該在Authorization頭中提供什么類型的授權(quán)信息?在包含401(Unauthorized)狀態(tài)行的應(yīng)答中這個(gè)頭是必需的。例如,response.setHeader("WWW-Authenticate", "BASIC realm=\"executives\"")。
? 注意Servlet一般不進(jìn)行這方面的處理,而是讓W(xué)eb服務(wù)器的專門機(jī)制來控制受密碼保護(hù)頁面的訪問(例如.htaccess)。
??
??
??
? 8.3 實(shí)例:內(nèi)容改變時(shí)自動(dòng)刷新頁面
??
? 下面這個(gè)Servlet用來計(jì)算大素?cái)?shù)。因?yàn)橛?jì)算非常大的數(shù)字(例如500位)可能要花不少時(shí)間,所以Servlet將立即返回已經(jīng)找到的結(jié)果,同時(shí)在后臺(tái)繼續(xù)計(jì)算。后臺(tái)計(jì)算使用一個(gè)優(yōu)先級(jí)較低的線程以避免過多地影響Web服務(wù)器的性能。如果計(jì)算還沒有完成,Servlet通過發(fā)送Refresh頭指示瀏覽器在幾秒之后繼續(xù)請(qǐng)求新的內(nèi)容。
??
? 注意,本例除了說明HTTP應(yīng)答頭的用處之外,還顯示了Servlet的另外兩個(gè)很有價(jià)值的功能。首先,它表明Servlet能夠處理多個(gè)并發(fā)的連接,每個(gè)都有自己的線程。Servlet維護(hù)了一份已有素?cái)?shù)計(jì)算請(qǐng)求的Vector表,通過查找素?cái)?shù)個(gè)數(shù)(素?cái)?shù)列表的長(zhǎng)度)和數(shù)字個(gè)數(shù)(每個(gè)素?cái)?shù)的長(zhǎng)度)將當(dāng)前請(qǐng)求和已有請(qǐng)求相匹配,把所有這些請(qǐng)求同步到這個(gè)列表上。第二,本例證明,在Servlet中維持請(qǐng)求之間的狀態(tài)信息是非常容易的。維持狀態(tài)信息在傳統(tǒng)的CGI編程中是一件很麻煩的事情。由于維持了狀態(tài)信息,瀏覽器能夠在刷新頁面時(shí)訪問到正在進(jìn)行的計(jì)算過程,同時(shí)也使得Servlet能夠保存一個(gè)有關(guān)最近請(qǐng)求結(jié)果的列表,當(dāng)一個(gè)新的請(qǐng)求指定了和最近請(qǐng)求相同的參數(shù)時(shí)可以立即返回結(jié)果。
??
? PrimeNumbers.java
??
? 注意,該Servlet要用到前面給出的ServletUtilities.java。另外還要用到:PrimeList.java,用于在后臺(tái)線程中創(chuàng)建一個(gè)素?cái)?shù)的Vector;Primes.java,用于隨機(jī)生成BigInteger類型的大數(shù)字,檢查它們是否是素?cái)?shù)。(此處略去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);
?? }
??
?? // 檢查是否存在同類型請(qǐng)求(已經(jīng)完成,或者正在計(jì)算)。
?? // 如存在,則返回現(xiàn)有結(jié)果而不是啟動(dòng)新的后臺(tái)線程。
?? 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);
?? }
?? }
? }?