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

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

評論