posts - 120,  comments - 19,  trackbacks - 0
一、Servlet和JSP概述
  作 者 : 仙人掌工作室
  
  
     1.1 Java Servlet及其特点
  
     Servlet是Java技术对CGI编程的回答。Servlet程序在服务器端运行,动态地生成Web页面。与传统的CGI和许多其他类似CGI的技术相比,Java Servlet具有更高的效率,更容易使用,功能更强大,具有更好的可移植性,更节省投资(更重要的是, Servlet程序员收入要比Perl程序员高:-):
  
  高效。
  
  在传统的CGI中,每个请求都要启动一个新的进程,如果CGI程序本身的执行时间较短,启动进程所需要的开销很可能反而超过实际执行时间。而在Servlet中,每个请求由一个轻量级的Java线程处理(而不是重量级的操作系统进程)。
  在传统CGI中,如果有N个并发的对同一CGI程序的请求,则该CGI程序的代码在内存中重复装载了N次;而对于Servlet,处理请求的是N个线程,只需要一份Servlet类代码。在性能优化方面,Servlet也比CGI有着更多的选择,比如缓冲以前的计算结果,保持数据库连接的活动,等等。
  
  
  方便。
  
  Servlet提供了大量的实用工具例程,例如自动地解析和解码HTML表单数据、读取和设置HTTP头、处理Cookie、跟踪会话状态等。
  
  
  功能强大。
  
  在Servlet中,许多使用传统CGI程序很难完成的任务都可以轻松地完成。例如,Servlet能够直接和Web服务器交互,而普通的CGI程序不能。Servlet还能够在各个程序之间共享数据,使得数据库连接池之类的功能很容易实现。
  
  
  可移植性好。
  
  Servlet用Java编写,Servlet API具有完善的标准。因此,为I-Planet Enterprise Server写的Servlet无需任何实质上的改动即可移植到Apache、Microsoft IIS或者WebStar。几乎所有的主流服务器都直接或通过插件支持Servlet。
  
  
  节省投资。
  
  不仅有许多廉价甚至免费的Web服务器可供个人或小规模网站使用,而且对于现有的服务器,如果它不支持Servlet的话,要加上这部分功能也往往是免费的(或只需要极少的投资)。
     1.2 JSP及其特点
  
     JavaServer Pages(JSP)是一种实现普通静态HTML和动态HTML混合编码的技术,有关JSP基础概念的说明请参见《JSP技术简介 》。
  
     许多由CGI程序生成的页面大部分仍旧是静态HTML,动态内容只在页面中有限的几个部分出现。但是包括Servlet在内的大多数CGI技术及其变种,总是通过程序生成整个页面。JSP使得我们可以分别创建这两个部分。例如,下面就是一个简单的JSP页面:
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
  <HTML>
  <HEAD><TITLE>欢迎访问网上商店</TITLE></HEAD>
  <BODY>
  <H1>欢迎</H1>
  <SMALL>欢迎,
  <!-- 首次访问的用户名字为"New User" -->
  <% out.println(Utils.getUserNameFromCookie(request)); %>
  要设置帐号信息,请点击
  <A HREF="Account-Settings.html">这里</A></SMALL>
  <P>
  页面的其余内容。.
  </BODY></HTML>
  
  
  
     下面是JSP和其他类似或相关技术的一个简单比较:
  
  JSP和Active Server Pages(ASP)相比
  
  Microsoft的ASP是一种和JSP类似的技术。JSP和ASP相比具有两方面的优点。首先,动态部分用Java编写,而不是VB Script或其他Microsoft语言,不仅功能更强大而且更易于使用。第二,JSP应用可以移植到其他操作系统和非Microsoft的Web服务器上。
  
  
  JSP和纯Servlet相比
  
  JSP并没有增加任何本质上不能用Servlet实现的功能。但是,在JSP中编写静态HTML更加方便,不必再用 println语句来输出每一行HTML代码。更重要的是,借助内容和外观的分离,页面制作中不同性质的任务可以方便地分开:比如,由页面设计专家进行HTML设计,同时留出供Servlet程序员插入动态内容的空间。
  
  
  JSP和服务器端包含(Server-Side Include,SSI)相比
  
  SSI是一种受到广泛支持的在静态HTML中引入外部代码的技术。JSP在这方面的支持更为完善,因为它可以用Servlet而不是独立的程序来生成动态内容。另外,SSI实际上只用于简单的包含,而不是面向那些能够处理表单数据、访问数据库的“真正的”程序。
  
  
  JSP和JavaScript相比
  
  JavaScript能够在客户端动态地生成HTML。虽然JavaScript很有用,但它只能处理以客户端环境为基础的动态信息。除了Cookie之外,HTTP状态和表单提交数据对JavaScript来说都是不可用的。另外,由于是在客户端运行,JavaScript不能访问服务器端资源,比如数据库、目录信息等等。
 2.1 安装Servlet和JSP开发工具
  
     要学习Servlet和JSP开发,首先你必须准备一个符合Java Servlet 2.1/2.2和JavaServer Pages1.0/1.1规范的开发环境。Sun提供免费的JavaServer Web Development Kit(JSWDK),可以从http://java.sun.com/products/servlet/ 下载。
  
     安装好JSWDK之后,你还要告诉javac,在编译文件的时候到哪里去寻找Servlet和JSP类。JSWDK安装指南对此有详细说明,但主要就是把servlet.jar和jsp.jar加入CLASSPATH。CLASSPATH是一个指示Java如何寻找类文件的环境变量,如果不设置CLASSPATH,Java在当前目录和标准系统库中寻找类;如果你自己设置了CLASSPATH,不要忘记包含当前目录(即在CLASSPATH中包含“.”)。
  
     另外,为了避免和其他开发者安装到同一Web服务器上的Servlet产生命名冲突,最好把自己的Servlet放入包里面。此时,把包层次结构中的顶级目录也加入CLASSPATH会带来不少方便。请参见下文具体说明。
  
     2.2 安装支持Servlet的Web服务器
  
     除了开发工具之外,你还要安装一个支持Java Servlet的Web服务器,或者在现有的Web服务器上安装Servlet软件包。如果你使用的是最新的Web服务器或应用服务器,很可能它已经有了所有必需的软件。请查看Web服务器的文档,或访问http://java.sun.com/products/servlet/industry.html 查看支持Servlet的服务器软件清单。
  
     虽然最终运行Servlet的往往是商业级的服务器,但是开始学习的时候,用一个能够在台式机上运行的免费系统进行开发和测试也足够了。下面是几种当前最受欢迎的产品。
  
  Apache Tomcat.
  
  Tomcat是Servlet 2.2和JSP 1.1规范的官方参考实现。Tomcat既可以单独作为小型Servlet、JSP测试服务器,也可以集成到Apache Web服务器。直到2000年早期,Tomcat还是唯一的支持Servlet 2.2和JSP 1.1规范的服务器,但已经有许多其它服务器宣布提供这方面的支持。
  
  Tomcat和Apache一样是免费的。不过,快速、稳定的Apache服务器安装和配置起来有点麻烦,Tomcat也有同样的缺点。和其他商业级Servlet引擎相比,配置Tomcat的工作量显然要多一点。具体请参见http://jakarta.apache.org/
  
  
  JavaServer Web Development Kit (JSWDK).
  
  JSWDK是Servlet 2.1和JSP 1.0的官方参考实现。把Servlet和JSP应用部署到正式运行它们的服务器之前,JSWDK可以单独作为小型的Servlet、JSP测试服务器。JSWDK也是免费的,而且具有很好的稳定性,但它的安装和配置也较为复杂。具体请参见http://java.sun.com/products/servlet/download.html
  
  
  Allaire JRun.
  
  JRun是一个Servlet和JSP引擎,它可以集成到Netscape Enterprise或FastTrack Server、IIS、Microsoft Personal Web Server、版本较低的Apache、O'eilly的WebSite或者StarNine Web STAR。最多支持5个并发连接的限制版本是免费的,商业版本中不存在这个限制,而且增加了远程管理控制台之类的功能。具体请参见http://www.allaire.com/products/jrun/
  
  
  New Atlanta 的ServletExec
  
  ServletExec是一个快速的Servlet和JSP引擎,它可以集成到大多数流行的Web服务器,支持平台包括Solaris、Windows、MacOS、HP-UX和Linux。ServletExec可以免费下载和使用,但许多高级功能和管理工具只有在购买了许可之后才可以使用。New Atlanta还提供一个免费的Servlet调试器,该调试器可以在许多流行的Java IDE下工作。具体请参见http://newatlanta.com/
  
  
  Gefion的LiteWebServer (LWS)
  
  LWS是一个支持Servlet 2.2和JSP 1.1的免费小型Web服务器。 Gefion还有一个免费的WAICoolRunner插件,利用该插件可以为Netscape FastTrack和Enterprise Server增加Servlet 2.2和JSP 1.1支持。具体请参见http://www.gefionsoftware.com/
  
  
  Sun的Java Web Server.
  
  该服务器全部用Java写成,而且是首先提供Servlet 2.1和JSP 1.0规范完整支持的Web服务器之一。虽然Sun现在已转向Netscape/I-Planet Server,不再发展Java Web Server,但它仍旧是一个广受欢迎的Servlet、JSP学习平台。要得到免费试用版本,请访问http://www.sun.com/software/jwebserver/try/
 
3.1 Servlet基本结构
  
     下面的代码显示了一个简单Servlet的基本结构。该Servlet处理的是GET请求,所谓的GET请求,如果你不熟悉HTTP,可以把它看成是当用户在浏览器地址栏输入URL、点击Web页面中的链接、提交没有指定METHOD的表单时浏览器所发出的请求。Servlet也可以很方便地处理POST请求。POST请求是提交那些指定了METHOD=“POST”的表单时所发出的请求,具体请参见稍后几节的讨论。
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  
  public class SomeServlet extends HttpServlet {
   public void doGet(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
  
   // 使用“request”读取和请求有关的信息(比如Cookies)
   // 和表单数据
  
   // 使用“response”指定HTTP应答状态代码和应答头
   // (比如指定内容类型,设置Cookie)
  
   PrintWriter out = response.getWriter();
   // 使用 "out"把应答内容发送到浏览器
   }
  }
  
  
  
  
     如果某个类要成为Servlet,则它应该从HttpServlet 继承,根据数据是通过GET还是POST发送,覆盖doGet、doPost方法之一或全部。doGet和doPost方法都有两个参数,分别为HttpServletRequest 类型和HttpServletResponse 类型。HttpServletRequest提供访问有关请求的信息的方法,例如表单数据、HTTP请求头等等。HttpServletResponse除了提供用于指定HTTP应答状态(200,404等)、应答头(Content-Type,Set-Cookie等)的方法之外,最重要的是它提供了一个用于向客户端发送数据的PrintWriter 。对于简单的Servlet来说,它的大部分工作是通过println语句生成向客户端发送的页面。
  
     注意doGet和doPost抛出两个异常,因此你必须在声明中包含它们。另外,你还必须导入java.io包(要用到PrintWriter等类)、javax.servlet包(要用到HttpServlet等类)以及javax.servlet.http包(要用到HttpServletRequest类和HttpServletResponse类)。
  
     最后,doGet和doPost这两个方法是由service方法调用的,有时你可能需要直接覆盖service方法,比如Servlet要处理GET和POST两种请求时。
  
     3.2 输出纯文本的简单Servlet
  
     下面是一个输出纯文本的简单Servlet。
  
     3.2.1 HelloWorld.java
  package hall;
  
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  
  public class HelloWorld extends HttpServlet {
   public void doGet(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   PrintWriter out = response.getWriter();
   out.println("Hello World");
   }
  }
  
  
  
  
     3.2.2 Servlet的编译和安装
  
     不同的Web服务器上安装Servlet的具体细节可能不同,请参考Web服务器文档了解更权威的说明。假定使用Java Web Server(JWS)2.0,则Servlet应该安装到JWS安装目录的servlets子目录下。在本文中,为了避免同一服务器上不同用户的Servlet命名冲突,我们把所有Servlet都放入一个独立的包hall中;如果你和其他人共用一个服务器,而且该服务器没有“虚拟服务器”机制来避免这种命名冲突,那么最好也使用包。把Servlet放入了包hall之后,HelloWorld.java实际上是放在servlets目录的hall子目录下。
  
     大多数其他服务器的配置方法也相似,除了JWS之外,本文的Servlet和JSP示例已经在BEA WebLogic和IBM WebSphere 3.0下经过测试。WebSphere具有优秀的虚拟服务器机制,因此,如果只是为了避免命名冲突的话并非一定要用包。
  
     对于没有使用过包的初学者,下面我们介绍编译包里面的类的两种方法。
  
     一种方法是设置CLASSPATH,使其指向实际存放Servlet的目录的上一级目录(Servlet主目录),然后在该目录中按正常的方式编译。例如,如果Servlet的主目录是C:\JavaWebServer\servlets,包的名字(即主目录下的子目录名字)是hall,在Windows下,编译过程如下:
   DOS> set CLASSPATH=C:\JavaWebServer\servlets;%CLASSPATH%
   DOS> cd C:\JavaWebServer\servlets\hall
   DOS> javac YourServlet.java
  
  
  
     第二种编译包里面的Servlet的方法是进入Servlet主目录,执行“javac directory\YourServlet.java”(Windows)或者“javac directory/YourServlet.java”(Unix)。例如,再次假定Servlet主目录是C:\JavaWebServer\servlets,包的名字是hall,在Windows中编译过程如下:
   DOS> cd C:\JavaWebServer\servlets
   DOS> javac hall\YourServlet.java
  
  
  
     注意在Windows下,大多数JDK 1.1版本的javac要求目录名字后面加反斜杠(\)。JDK1.2已经改正这个问题,然而由于许多Web服务器仍旧使用JDK 1.1,因此大量的Servlet开发者仍旧在使用JDK 1.1。
  
     最后,Javac还有一个高级选项用于支持源代码和.class文件的分开放置,即你可以用javac的“-d”选项把.class文件安装到Web服务器所要求的目录。
  
     3.2.3 运行Servlet
  
     在Java Web Server下,Servlet应该放到JWS安装目录的servlets子目录下,而调用Servlet的URL是http://host/servlet/ServletName。注意子目录的名字是servlets(带“s”),而URL使用的是“servlet”。由于HelloWorld Servlet放入包hall,因此调用它的URL应该是http://host/servlet/hall.HelloWorld。在其他的服务器上,安装和调用Servlet的方法可能略有不同。
  
     大多数Web服务器还允许定义Servlet的别名,因此Servlet也可能用http://host/any-path/any-file.html形式的URL调用。具体如何进行配置完全依赖于服务器类型,请参考服务器文档了解细节。
  
     3.3 输出HTML的Servlet
  
     大多数Servlet都输出HTML,而不象上例一样输出纯文本。要输出HTML还有两个额外的步骤要做:告诉浏览器接下来发送的是HTML;修改println语句构造出合法的HTML页面。
  
     第一步通过设置Content-Type(内容类型)应答头完成。一般地,应答头可以通过HttpServletResponse的setHeader方法设置,但由于设置内容类型是一个很频繁的操作,因此Servlet API提供了一个专用的方法setContentType。注意设置应答头应该在通过PrintWriter发送内容之前进行。下面是一个实例:
  
     HelloWWW.java
  package hall;
  
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  
  public class HelloWWW extends HttpServlet {
   public void doGet(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   response.setContentType("text/html");
   PrintWriter out = response.getWriter();
   out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " +
   "Transitional//EN\">\n" +
   "<HTML>\n" +
   "<HEAD><TITLE>Hello WWW</TITLE></HEAD>\n" +
   "<BODY>\n" +
   "<H1>Hello WWW</H1>\n" +
   "</BODY></HTML>");
   }
  }
  
  
  
  
     3.4 几个HTML工具函数
  
     通过println语句输出HTML并不方便,根本的解决方法是使用JavaServer Pages(JSP)。然而,对于标准的Servlet来说,由于Web页面中有两个部分(DOCTYPE和HEAD)一般不会改变,因此可以用工具函数来封装生成这些内容的代码。
  
     虽然大多数主流浏览器都会忽略DOCTYPE行,但严格地说HTML规范是要求有DOCTYPE行的,它有助于HTML语法检查器根据所声明的HTML版本检查HTML文档合法性。在许多Web页面中,HEAD部分只包含<TITLE>。虽然许多有经验的编写者都会在HEAD中包含许多META标记和样式声明,但这里只考虑最简单的情况。
  
     下面的Java方法只接受页面标题为参数,然后输出页面的DOCTYPE、HEAD、TITLE部分。清单如下:
  
     ServletUtilities.java
  package hall;
  
  public class ServletUtilities {
   public static final String DOCTYPE =
   "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">";
  
   public static String headWithTitle(String title) {
   return(DOCTYPE + "\n" +
   "<HTML>\n" +
   "<HEAD><TITLE>" + title + "</TITLE></HEAD>\n");
   }
  
   // 其他工具函数的代码在本文后面介绍
  }
  
  
  
  
     HelloWWW2.java
  
     下面是应用了ServletUtilities之后重写HelloWWW类得到的HelloWWW2:
  package hall;
  
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  
  public class HelloWWW2 extends HttpServlet {
   public void doGet(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   response.setContentType("text/html");
   PrintWriter out = response.getWriter();
   out.println(ServletUtilities.headWithTitle("Hello WWW") +
   "<BODY>\n" +
   "<H1>Hello WWW</H1>\n" +
   "</BODY></HTML>");
   }
  }
4.1 表单数据概述
  
     如果你曾经使用过Web搜索引擎,或者浏览过在线书店、股票价格、机票信息,或许会留意到一些古怪的URL,比如“http://host/path?user=Marty+Hall&origin=bwi&dest=lax”。这个URL中位于问号后面的部分,即“user=Marty+Hall&origin=bwi&dest=lax”,就是表单数据,这是将Web页面数据发送给服务器程序的最常用方法。对于GET请求,表单数据附加到URL的问号后面(如上例所示);对于POST请求,表单数据用一个单独的行发送给服务器。
  
     以前,从这种形式的数据提取出所需要的表单变量是CGI编程中最麻烦的事情之一。首先,GET请求和POST请求的数据提取方法不同:对于GET请求,通常要通过QUERY_STRING环境变量提取数据;对于POST请求,则一般通过标准输入提取数据。第二,程序员必须负责在“&”符号处截断变量名字-变量值对,再分离出变量名字(等号左边)和变量值(等号右边)。第三,必须对变量值进行URL反编码操作。因为发送数据的时候,字母和数字以原来的形式发送,但空格被转换成加号,其他字符被转换成“%XX”形式,其中XX是十六进制表示的字符ASCII(或者ISO Latin-1)编码值。例如,如果HTML表单中名为“users”的域值为“~hall, ~gates, and ~mcnealy”,则实际向服务器发送的数据为“users=%7Ehall%2C+%7Egates%2C+and+%7Emcnealy”。最后,即第四个导致解析表单数据非常困难的原因在于,变量值既可能被省略(如“param1=val1&param2=&param3=val3”),也有可能一个变量拥有一个以上的值,即同一个变量可能出现一次以上(如“param1=val1&param2=val2&param1=val3”)。
  
     Java Servlet的好处之一就在于所有上述解析操作都能够自动完成。只需要简单地调用一下HttpServletRequest的getParameter方法、在调用参数中提供表单变量的名字(大小写敏感)即可,而且GET请求和POST请求的处理方法完全相同。
  
     getParameter方法的返回值是一个字符串,它是参数中指定的变量名字第一次出现所对应的值经反编码得到得字符串(可以直接使用)。如果指定的表单变量存在,但没有值,getParameter返回空字符串;如果指定的表单变量不存在,则返回null。如果表单变量可能对应多个值,可以用getParameterValues来取代getParameter。getParameterValues能够返回一个字符串数组。
  
     最后,虽然在实际应用中Servlet很可能只会用到那些已知名字的表单变量,但在调试环境中,获得完整的表单变量名字列表往往是很有用的,利用getParamerterNames方法可以方便地实现这一点。getParamerterNames返回的是一个Enumeration,其中的每一项都可以转换为调用getParameter的字符串。
  
     4.2 实例:读取三个表单变量
  
     下面是一个简单的例子,它读取三个表单变量param1、param2和param3,并以HTML列表的形式列出它们的值。请注意,虽然在发送应答内容之前必须指定应答类型(包括内容类型、状态以及其他HTTP头信息),但Servlet对何时读取请求内容却没有什么要求。
  
     另外,我们也可以很容易地把Servlet做成既能处理GET请求,也能够处理POST请求,这只需要在doPost方法中调用doGet方法,或者覆盖service方法(service方法调用doGet、doPost、doHead等方法)。在实际编程中这是一种标准的方法,因为它只需要很少的额外工作,却能够增加客户端编码的灵活性。
  
     如果你习惯用传统的CGI方法,通过标准输入读取POST数据,那么在Servlet中也有类似的方法,即在HttpServletRequest上调用getReader或者getInputStream,但这种方法对普通的表单变量来说太麻烦。然而,如果是要上载文件,或者POST数据是通过专门的客户程序而不是HTML表单发送,那么就要用到这种方法。
  
     注意用第二种方法读取POST数据时,不能再用getParameter来读取这些数据。
  
     ThreeParams.java
  package hall;
  
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.util.*;
  
  public class ThreeParams extends HttpServlet {
   public void doGet(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   response.setContentType("text/html");
   PrintWriter out = response.getWriter();
   String title = "读取三个请求参数";
   out.println(ServletUtilities.headWithTitle(title) +
   "<BODY>\n" +
   "<H1 ALIGN=CENTER>" + title + "</H1>\n" +
   "<UL>\n" +
   " <LI>param1: "
   + request.getParameter("param1") + "\n" +
   " <LI>param2: "
   + request.getParameter("param2") + "\n" +
   " <LI>param3: "
   + request.getParameter("param3") + "\n" +
   "</UL>\n" +
   "</BODY></HTML>");
   }
  
   public void doPost(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   doGet(request, response);
   }
  }
  
  
  
  
     4.3 实例:输出所有的表单数据
  
     下面这个例子寻找表单所发送的所有变量名字,并把它们放入表格中,没有值或者有多个值的变量都突出显示。
  
     首先,程序通过HttpServletRequest的getParameterNames方法得到所有的变量名字,getParameterNames返回的是一个Enumeration。接下来,程序用循环遍历这个Enumeration,通过hasMoreElements确定何时结束循环,利用nextElement得到Enumeration中的各个项。由于nextElement返回的是一个Object,程序把它转换成字符串后再用这个字符串来调用getParameterValues。
  
     getParameterValues返回一个字符串数组,如果这个数组只有一个元素且等于空字符串,说明这个表单变量没有值,Servlet以斜体形式输出“No Value”;如果数组元素个数大于1,说明这个表单变量有多个值,Servlet以HTML列表形式输出这些值;其他情况下Servlet直接把变量值放入表格。
  
     ShowParameters.java
  
     注意,ShowParameters.java用到了前面介绍过的ServletUtilities.java。
  package hall;
  
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.util.*;
  
  public class ShowParameters extends HttpServlet {
   public void doGet(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   response.setContentType("text/html");
   PrintWriter out = response.getWriter();
   String title = "读取所有请求参数";
   out.println(ServletUtilities.headWithTitle(title) +
   "<BODY BGCOLOR=\"#FDF5E6\">\n" +
   "<H1 ALIGN=CENTER>" + title + "</H1>\n" +
   "<TABLE BORDER=1 ALIGN=CENTER>\n" +
   "<TR BGCOLOR=\"#FFAD00\">\n" +
   "<TH>参数名字<TH>参数值");
   Enumeration paramNames = request.getParameterNames();
   while(paramNames.hasMoreElements()) {
   String paramName = (String)paramNames.nextElement();
   out.println("<TR><TD>" + paramName + "\n<TD>");
   String[] paramValues = request.getParameterValues(paramName);
   if (paramValues.length == 1) {
   String paramValue = paramValues[0];
   if (paramValue.length() == 0)
   out.print("<I>No Value</I>");
   else
   out.print(paramValue);
   } else {
   out.println("<UL>");
   for(int i=0; i<paramValues.length; i++) {
   out.println("<LI>" + paramValues[i]);
   }
   out.println("</UL>");
   }
   }
   out.println("</TABLE>\n</BODY></HTML>");
   }
  
   public void doPost(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   doGet(request, response);
   }
  }
  
  
  
  
     测试表单
  
     下面是向上述Servlet发送数据的表单PostForm.html。就像所有包含密码输入域的表单一样,该表单用POST方法发送数据。我们可以看到,在Servlet中同时实现doGet和doPost这两种方法为表单制作带来了方便。
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
  <HTML>
  <HEAD>
   <TITLE>示例表单</TITLE>
  </HEAD>
  
  <BODY BGCOLOR="#FDF5E6">
  <H1 ALIGN="CENTER">用POST方法发送数据的表单</H1>
  
  <FORM ACTION="/servlet/hall.ShowParameters"
   METHOD="POST">
   Item Number:
   <INPUT TYPE="TEXT" NAME="itemNum"><BR>
   Quantity:
   <INPUT TYPE="TEXT" NAME="quantity"><BR>
   Price Each:
   <INPUT TYPE="TEXT" NAME="price" VALUE="$"><BR>
   <HR>
   First Name:
   <INPUT TYPE="TEXT" NAME="firstName"><BR>
   Last Name:
   <INPUT TYPE="TEXT" NAME="lastName"><BR>
   Middle Initial:
   <INPUT TYPE="TEXT" NAME="initial"><BR>
   Shipping Address:
   <TEXTAREA NAME="address" ROWS=3 COLS=40></TEXTAREA><BR>
   Credit Card:<BR>
   <INPUT TYPE="RADIO" NAME="cardType"
   VALUE="Visa">Visa<BR>
   <INPUT TYPE="RADIO" NAME="cardType"
   VALUE="Master Card">Master Card<BR>
   <INPUT TYPE="RADIO" NAME="cardType"
   VALUE="Amex">American Express<BR>
   <INPUT TYPE="RADIO" NAME="cardType"
   VALUE="Discover">Discover<BR>
   <INPUT TYPE="RADIO" NAME="cardType"
   VALUE="Java SmartCard">Java SmartCard<BR>
   Credit Card Number:
   <INPUT TYPE="PASSWORD" NAME="cardNum"><BR>
   Repeat Credit Card Number:
   <INPUT TYPE="PASSWORD" NAME="cardNum"><BR><BR>
   <CENTER>
   <INPUT TYPE="SUBMIT" VALUE="Submit Order">
   </CENTER>
  </FORM>
  
  </BODY>
  </HTML>
5.1 HTTP请求头概述
  
     HTTP客户程序(例如浏览器),向服务器发送请求的时候必须指明请求类型(一般是GET或者POST)。如有必要,客户程序还可以选择发送其他的请求头。大多数请求头并不是必需的,但Content-Length除外。对于POST请求来说Content-Length必须出现。
  
     下面是一些最常见的请求头:
  
  Accept:浏览器可接受的MIME类型。
  Accept-Charset:浏览器可接受的字符集。
  Accept-Encoding:浏览器能够进行解码的数据编码方式,比如gzip。Servlet能够向支持gzip的浏览器返回经gzip编码的HTML页面。许多情形下这可以减少5到10倍的下载时间。
  Accept-Language:浏览器所希望的语言种类,当服务器能够提供一种以上的语言版本时要用到。
  Authorization:授权信息,通常出现在对服务器发送的WWW-Authenticate头的应答中。
  Connection:表示是否需要持久连接。如果Servlet看到这里的值为“Keep-Alive”,或者看到请求使用的是HTTP 1.1(HTTP 1.1默认进行持久连接),它就可以利用持久连接的优点,当页面包含多个元素时(例如Applet,图片),显著地减少下载所需要的时间。要实现这一点,Servlet需要在应答中发送一个Content-Length头,最简单的实现方法是:先把内容写入ByteArrayOutputStream,然后在正式写出内容之前计算它的大小。
  Content-Length:表示请求消息正文的长度。
  Cookie:这是最重要的请求头信息之一,参见后面《Cookie处理》一章中的讨论。
  From:请求发送者的email地址,由一些特殊的Web客户程序使用,浏览器不会用到它。
  Host:初始URL中的主机和端口。
  If-Modified-Since:只有当所请求的内容在指定的日期之后又经过修改才返回它,否则返回304“Not Modified”应答。
  Pragma:指定“no-cache”值表示服务器必须返回一个刷新后的文档,即使它是代理服务器而且已经有了页面的本地拷贝。
  Referer:包含一个URL,用户从该URL代表的页面出发访问当前请求的页面。
  User-Agent:浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用。
  UA-Pixels,UA-Color,UA-OS,UA-CPU:由某些版本的IE浏览器所发送的非标准的请求头,表示屏幕大小、颜色深度、操作系统和CPU类型。
     有关HTTP头完整、详细的说明,请参见http://www.w3.org/Protocols/ 的HTTP规范。
  
     5.2 在Servlet中读取请求头
  
     在Servlet中读取HTTP头是非常方便的,只需要调用一下HttpServletRequest的getHeader方法即可。如果客户请求中提供了指定的头信息,getHeader返回对应的字符串;否则,返回null。部分头信息经常要用到,它们有专用的访问方法:getCookies方法返回Cookie头的内容,经解析后存放在Cookie对象的数组中,请参见后面有关Cookie章节的讨论;getAuthType和getRemoteUser方法分别读取Authorization头中的一部分内容;getDateHeader和getIntHeader方法读取指定的头,然后返回日期值或整数值。
  
     除了读取指定的头之外,利用getHeaderNames还可以得到请求中所有头名字的一个Enumeration对象。
  
     最后,除了查看请求头信息之外,我们还可以从请求主命令行获得一些信息。getMethod方法返回请求方法,请求方法通常是GET或者POST,但也有可能是HEAD、PUT或者DELETE。getRequestURI方法返回URI(URI是URL的从主机和端口之后到表单数据之前的那一部分)。getRequestProtocol返回请求命令的第三部分,一般是“HTTP/1.0”或者“HTTP/1.1”。
  
     5.3 实例:输出所有的请求头
  
     下面的Servlet实例把所有接收到的请求头和它的值以表格的形式输出。另外,该Servlet还会输出主请求命令的三个部分:请求方法,URI,协议/版本。
  
     ShowRequestHeaders.java
  package hall;
  
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.util.*;
  
  public class ShowRequestHeaders extends HttpServlet {
   public void doGet(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   response.setContentType("text/html");
   PrintWriter out = response.getWriter();
   String title = "显示所有请求头";
   out.println(ServletUtilities.headWithTitle(title) +
   "<BODY BGCOLOR=\"#FDF5E6\">\n" +
   "<H1 ALIGN=CENTER>" + title + "</H1>\n" +
   "<B>Request Method: </B>" +
   request.getMethod() + "<BR>\n" +
   "<B>Request URI: </B>" +
   request.getRequestURI() + "<BR>\n" +
   "<B>Request Protocol: </B>" +
   request.getProtocol() + "<BR><BR>\n" +
   "<TABLE BORDER=1 ALIGN=CENTER>\n" +
   "<TR BGCOLOR=\"#FFAD00\">\n" +
   "<TH>Header Name<TH>Header Value");
   Enumeration headerNames = request.getHeaderNames();
   while(headerNames.hasMoreElements()) {
   String headerName = (String)headerNames.nextElement();
   out.println("<TR><TD>" + headerName);
   out.println(" <TD>" + request.getHeader(headerName));
   }
   out.println("</TABLE>\n</BODY></HTML>");
   }
  
   public void doPost(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   doGet(request, response);
   }
  }
6.1 CGI变量概述
  
     如果你是从传统的CGI编程转而学习Java Servlet,或许已经习惯了“CGI变量”这一概念。CGI变量汇集了各种有关请求的信息:
  
  部分来自HTTP请求命令和请求头,例如Content-Length头;
  部分来自Socket本身,例如主机的名字和IP地址;
  也有部分与服务器安装配置有关,例如URL到实际路径的映射。
     6.2 标准CGI变量的Servlet等价表示
  
     下表假定request对象是提供给doGet和doPost方法的HttpServletRequest类型对象。 CGI变量 含义 从doGet或doPost访问
  AUTH_TYPE 如果提供了Authorization头,这里指定了具体的模式(basic或者digest)。 request.getAuthType()
  CONTENT_LENGTH 只用于POST请求,表示所发送数据的字节数。 严格地讲,等价的表达方式应该是String.valueOf(request.getContentLength())(返回一个字符串)。但更常见的是用request.getContentLength()返回含义相同的整数。
  CONTENT_TYPE 如果指定的话,表示后面所跟数据的类型。 request.getContentType()
  DOCUMENT_ROOT 与http://host/对应的路径。 getServletContext().getRealPath("/")
  注意低版本Servlet规范中的等价表达方式是request.getRealPath("/")。
  
  HTTP_XXX_YYY 访问任意HTTP头。 request.getHeader("Xxx-Yyy")
  PATH_INFO URL中的附加路径信息,即URL中Servlet路径之后、查询字符串之前的那部分。 request.getPathInfo()
  PATH_TRANSLATED 映射到服务器实际路径之后的路径信息。 request.getPathTranslated()
  QUERY_STRING 这是字符串形式的附加到URL后面的查询字符串,数据仍旧是URL编码的。在Servlet中很少需要用到未经解码的数据,一般使用getParameter访问各个参数。 request.getQueryString()
  REMOTE_ADDR 发出请求的客户机的IP地址。 request.getRemoteAddr()
  REMOTE_HOST 发出请求的客户机的完整的域名,如java.sun.com。如果不能确定该域名,则返回IP地址。 request.getRemoteHost()
  REMOTE_USER 如果提供了Authorization头,则代表其用户部分。它代表发出请求的用户的名字。 request.getRemoteUser()
  REQUEST_METHOD 请求类型。通常是GET或者POST。但偶尔也会出现HEAD,PUT, DELETE,OPTIONS,或者 TRACE. request.getMethod()
  SCRIPT_NAME URL中调用Servlet的那一部分,不包含附加路径信息和查询字符串。 request.getServletPath()
  SERVER_NAME Web服务器名字。 request.getServerName()
  SERVER_PORT 服务器监听的端口。 严格地说,等价表达应该是返回字符串的String.valueOf(request.getServerPort())。但经常使用返回整数值的request.getServerPort()。
  SERVER_PROTOCOL 请求命令中的协议名字和版本(即HTTP/1.0或HTTP/1.1)。 request.getProtocol()
  SERVER_SOFTWARE Servlet引擎的名字和版本。 getServletContext().getServerInfo()
  
  
     6.3 实例:读取CGI变量
  
     下面这个Servlet创建一个表格,显示除了HTTP_XXX_YYY之外的所有CGI变量。HTTP_XXX_YYY是HTTP请求头信息,请参见上一节介绍。
  
     ShowCGIVariables.java
  package hall;
  
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.util.*;
  
  public class ShowCGIVariables extends HttpServlet {
   public void doGet(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   response.setContentType("text/html");
   PrintWriter out = response.getWriter();
   String[][] variables =
   { { "AUTH_TYPE", request.getAuthType() },
   { "CONTENT_LENGTH", String.valueOf(request.getContentLength()) },
   { "CONTENT_TYPE", request.getContentType() },
   { "DOCUMENT_ROOT", getServletContext().getRealPath("/") },
   { "PATH_INFO", request.getPathInfo() },
   { "PATH_TRANSLATED", request.getPathTranslated() },
   { "QUERY_STRING", request.getQueryString() },
   { "REMOTE_ADDR", request.getRemoteAddr() },
   { "REMOTE_HOST", request.getRemoteHost() },
   { "REMOTE_USER", request.getRemoteUser() },
   { "REQUEST_METHOD", request.getMethod() },
   { "SCRIPT_NAME", request.getServletPath() },
   { "SERVER_NAME", request.getServerName() },
   { "SERVER_PORT", String.valueOf(request.getServerPort()) },
   { "SERVER_PROTOCOL", request.getProtocol() },
   { "SERVER_SOFTWARE", getServletContext().getServerInfo() }
   };
   String title = "显示CGI变量";
   out.println(ServletUtilities.headWithTitle(title) +
   "<BODY BGCOLOR=\"#FDF5E6\">\n" +
   "<H1 ALIGN=CENTER>" + title + "</H1>\n" +
   "<TABLE BORDER=1 ALIGN=CENTER>\n" +
   "<TR BGCOLOR=\"#FFAD00\">\n" +
   "<TH>CGI Variable Name<TH>Value");
   for(int i=0; i<variables.length; i++) {
   String varName = variables[i][0];
   String varValue = variables[i][1];
   if (varValue == null)
   varValue = "<I>Not specified</I>";
   out.println("<TR><TD>" + varName + "<TD>" + varValue);
   }
   out.println("</TABLE></BODY></HTML>");
   }
  
   public void doPost(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   doGet(request, response);
   }
  }
7.1 状态代码概述
  
     Web服务器响应浏览器或其他客户程序的请求时,其应答一般由以下几个部分组成:一个状态行,几个应答头,一个空行,内容文档。下面是一个最简单的应答:
  HTTP/1.1 200 OK
  Content-Type: text/plain
  
  Hello World
  
  
  
  
     状态行包含HTTP版本、状态代码、与状态代码对应的简短说明信息。在大多数情况下,除了Content-Type之外的所有应答头都是可选的。但Content-Type是必需的,它描述的是后面文档的MIME类型。虽然大多数应答都包含一个文档,但也有一些不包含,例如对HEAD请求的应答永远不会附带文档。有许多状态代码实际上用来标识一次失败的请求,这些应答也不包含文档(或只包含一个简短的错误信息说明)。
  
     Servlet可以利用状态代码来实现许多功能。例如,可以把用户重定向到另一个网站;可以指示出后面的文档是图片、PDF文件或HTML文件;可以告诉用户必须提供密码才能访问文档;等等。这一部分我们将具体讨论各种状态代码的含义以及利用这些代码可以做些什么。
  
     7.2 设置状态代码
  
     如前所述,HTTP应答状态行包含HTTP版本、状态代码和对应的状态信息。由于状态信息直接和状态代码相关,而HTTP版本又由服务器确定,因此需要Servlet设置的只有一个状态代码。
  
     Servlet设置状态代码一般使用HttpServletResponse的setStatus方法。setStatus方法的参数是一个整数(即状态代码),不过为了使得代码具有更好的可读性,可以用HttpServletResponse中定义的常量来避免直接使用整数。这些常量根据HTTP 1.1中的标准状态信息命名,所有的名字都加上了SC前缀(Status Code的缩写)并大写,同时把空格转换成了下划线。也就是说,与状态代码404对应的状态信息是“Not Found”,则HttpServletResponse中的对应常量名字为SC_NOT_FOUND。但有两个例外:和状态代码302对应的常量根据HTTP 1.0命名,而307没有对应的常量。
  
     设置状态代码并非总是意味着不要再返回文档。例如,虽然大多数服务器返回404应答时会输出简单的“File Not Found”信息,但Servlet也可以定制这个应答。不过,定制应答时应当在通过PrintWriter发送任何内容之前先调用response.setStatus。
  
     虽然设置状态代码一般使用的是response.setStauts(int)方法,但为了简单起见,HttpServletResponse为两种常见的情形提供了专用方法:sendError方法生成一个404应答,同时生成一个简短的HTML错误信息文档;sendRedirect方法生成一个302应答,同时在Location头中指示新文档的URL。
  
     7.3 HTTP 1.1状态代码及其含义
  
     下表显示了常见的HTTP 1.1状态代码以及它们对应的状态信息和含义。
  
     应当谨慎地使用那些只有HTTP 1.1支持的状态代码,因为许多浏览器还只能够支持HTTP 1.0。如果你使用了HTTP 1.1特有的状态代码,最好能够检查一下请求的HTTP版本号(通过HttpServletRequest的getProtocol方法)。 状态代码 状态信息 含义
  100 Continue 初始的请求已经接受,客户应当继续发送请求的其余部分。(HTTP 1.1新)
  101 Switching Protocols 服务器将遵从客户的请求转换到另外一种协议(HTTP 1.1新)
  200 OK 一切正常,对GET和POST请求的应答文档跟在后面。如果不用setStatus设置状态代码,Servlet默认使用202状态代码。
  201 Created 服务器已经创建了文档,Location头给出了它的URL。
  202 Accepted 已经接受请求,但处理尚未完成。
  203 Non-Authoritative Information 文档已经正常地返回,但一些应答头可能不正确,因为使用的是文档的拷贝(HTTP 1.1新)。
  204 No Content 没有新文档,浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的。
  205 Reset Content 没有新的内容,但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容(HTTP 1.1新)。
  206 Partial Content 客户发送了一个带有Range头的GET请求,服务器完成了它(HTTP 1.1新)。
  300 Multiple Choices 客户请求的文档可以在多个位置找到,这些位置已经在返回的文档内列出。如果服务器要提出优先选择,则应该在Location应答头指明。
  301 Moved Permanently 客户请求的文档在其他地方,新的URL在Location头中给出,浏览器应该自动地访问新的URL。
  302 Found 类似于301,但新的URL应该被视为临时性的替代,而不是永久性的。注意,在HTTP1.0中对应的状态信息是“Moved Temporatily”,而HttpServletResponse中相应的常量是SC_MOVED_TEMPORARILY,而不是SC_FOUND。
  出现该状态代码时,浏览器能够自动访问新的URL,因此它是一个很有用的状态代码。为此,Servlet提供了一个专用的方法,即sendRedirect。使用response.sendRedirect(url)比使用response.setStatus(response.SC_MOVED_TEMPORARILY)和response.setHeader("Location",url)更好。这是因为:
  
  首先,代码更加简洁。
  第二,使用sendRedirect,Servlet会自动构造一个包含新链接的页面(用于那些不能自动重定向的老式浏览器)。
  最后,sendRedirect能够处理相对URL,自动把它们转换成绝对URL。
  注意这个状态代码有时候可以和301替换使用。例如,如果浏览器错误地请求http://host/~user(缺少了后面的斜杠),有的服务器返回301,有的则返回302。
  
  严格地说,我们只能假定只有当原来的请求是GET时浏览器才会自动重定向。请参见307。
  
  303 See Other 类似于301/302,不同之处在于,如果原来的请求是POST,Location头指定的重定向目标文档应该通过GET提取(HTTP 1.1新)。
  304 Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
  305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取(HTTP 1.1新)。
  307 Temporary Redirect 和302(Found)相同。许多浏览器会错误地响应302应答进行重定向,即使原来的请求是POST,即使它实际上只能在POST请求的应答是303时才能重定向。由于这个原因,HTTP 1.1新增了307,以便更加清除地区分几个状态代码:当出现303应答时,浏览器可以跟随重定向的GET和POST请求;如果是307应答,则浏览器只能跟随对GET请求的重定向。
  注意,HttpServletResponse中没有为该状态代码提供相应的常量。(HTTP 1.1新)
  
  400 Bad Request 请求出现语法错误。
  401 Unauthorized 客户试图未经授权访问受密码保护的页面。应答中会包含一个WWW-Authenticate头,浏览器据此显示用户名字/密码对话框,然后在填写合适的Authorization头后再次发出请求。
  403 Forbidden 资源不可用。服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致。
  404 Not Found 无法找到指定位置的资源。这也是一个常用的应答,HttpServletResponse专门提供了相应的方法:sendError(message)。
  405 Method Not Allowed 请求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)对指定的资源不适用。(HTTP 1.1新)
  406 Not Acceptable 指定的资源已经找到,但它的MIME类型和客户在Accpet头中所指定的不兼容(HTTP 1.1新)。
  407 Proxy Authentication Required 类似于401,表示客户必须先经过代理服务器的授权。(HTTP 1.1新)
  408 Request Timeout 在服务器许可的等待时间内,客户一直没有发出任何请求。客户可以在以后重复同一请求。(HTTP 1.1新)
  409 Conflict 通常和PUT请求有关。由于请求和资源的当前状态相冲突,因此请求不能成功。(HTTP 1.1新)
  410 Gone 所请求的文档已经不再可用,而且服务器不知道应该重定向到哪一个地址。它和404的不同在于,返回407表示文档永久地离开了指定的位置,而404表示由于未知的原因文档不可用。(HTTP 1.1新)
  411 Length Required 服务器不能处理请求,除非客户发送一个Content-Length头。(HTTP 1.1新)
  412 Precondition Failed 请求头中指定的一些前提条件失败(HTTP 1.1新)。
  413 Request Entity Too Large 目标文档的大小超过服务器当前愿意处理的大小。如果服务器认为自己能够稍后再处理该请求,则应该提供一个Retry-After头(HTTP 1.1新)。
  414 Request URI Too Long URI太长(HTTP 1.1新)。
  416 Requested Range Not Satisfiable 服务器不能满足客户在请求中指定的Range头。(HTTP 1.1新)
  500 Internal Server Error 服务器遇到了意料不到的情况,不能完成客户的请求。
  501 Not Implemented 服务器不支持实现请求所需要的功能。例如,客户发出了一个服务器不支持的PUT请求。
  502 Bad Gateway 服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答。
  503 Service Unavailable 服务器由于维护或者负载过重未能应答。例如,Servlet可能在数据库连接池已满的情况下返回503。服务器返回503时可以提供一个Retry-After头。
  504 Gateway Timeout 由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答。(HTTP 1.1新)
  505 HTTP Version Not Supported 服务器不支持请求中所指明的HTTP版本。(HTTP 1.1新)
  
  
     7.4 实例:访问多个搜索引擎
  
     下面这个例子用到了除200之外的另外两个常见状态代码:302和404。302通过sendRedirect方法设置,404通过sendError方法设置。
  
     在这个例子中,首先出现的HTML表单用来选择搜索引擎、搜索字符串、每页显示的搜索结果数量。表单提交后,Servlet提取这三个变量,按照所选择的搜索引擎的要求构造出包含这些变量的URL,然后把用户重定向到这个URL。如果用户不能正确地选择搜索引擎,或者利用其他表单发送了一个不认识的搜索引擎名字,则返回一个提示搜索引擎找不到的404页面。
  
     SearchEngines.java
  
     注意:这个Servlet要用到后面给出的SearchSpec类,SearchSpec的功能是构造适合不同搜索引擎的URL。
  package hall;
  
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.net.*;
  
  public class SearchEngines extends HttpServlet {
   public void doGet(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   // getParameter自动解码URL编码的查询字符串。由于我们
   // 要把查询字符串发送给另一个服务器,因此再次使用
   // URLEncoder进行URL编码
   String searchString =
   URLEncoder.encode(request.getParameter("searchString"));
   String numResults =
   request.getParameter("numResults");
   String searchEngine =
   request.getParameter("searchEngine");
   SearchSpec[] commonSpecs = SearchSpec.getCommonSpecs();
   for(int i=0; i<commonSpecs.length; i++) {
   SearchSpec searchSpec = commonSpecs[i];
   if (searchSpec.getName().equals(searchEngine)) {
   String url =
   response.encodeURL(searchSpec.makeURL(searchString,
   numResults));
   response.sendRedirect(url);
   return;
   }
   }
   response.sendError(response.SC_NOT_FOUND,
   "No recognized search engine specified.");
   }
  
   public void doPost(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   doGet(request, response);
   }
  }
  
  
  
  
     SearchSpec.java
  package hall;
  
  class SearchSpec {
   private String name, baseURL, numResultsSuffix;
  
   private static SearchSpec[] commonSpecs =
   { new SearchSpec("google",
   "http://www.google.com/search?q=",
   "&num="),
   new SearchSpec("infoseek",
   "http://infoseek.go.com/Titles?qt=",
   "&nh="),
   new SearchSpec("lycos",
   "http://lycospro.lycos.com/cgi-bin/pursuit?query=",
   "&maxhits="),
   new SearchSpec("hotbot",
   "http://www.hotbot.com/?MT=",
   "&DC=")
   };
  
   public SearchSpec(String name,
   String baseURL,
   String numResultsSuffix) {
   this.name = name;
   this.baseURL = baseURL;
   this.numResultsSuffix = numResultsSuffix;
   }
  
   public String makeURL(String searchString, String numResults) {
   return(baseURL + searchString + numResultsSuffix + numResults);
   }
  
   public String getName() {
   return(name);
   }
  
   public static SearchSpec[] getCommonSpecs() {
   return(commonSpecs);
   }
  }
  
  
  
  
     SearchEngines.html
  
     下面是调用上述Servlet的HTML表单。
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
  <HTML>
  <HEAD>
   <TITLE>访问多个搜索引擎</TITLE>
  </HEAD>
  
  <BODY BGCOLOR="#FDF5E6">
  
  <FORM ACTION="/servlet/hall.SearchEngines">
   <CENTER>
   搜索关键字:
   <INPUT TYPE="TEXT" NAME="searchString"><BR>
   每页显示几个查询结果:
   <INPUT TYPE="TEXT" NAME="numResults"
   VALUE=10 SIZE=3><BR>
   <INPUT TYPE="RADIO" NAME="searchEngine"
   VALUE="google">
   Google |
   <INPUT TYPE="RADIO" NAME="searchEngine"
   VALUE="infoseek">
   Infoseek |
   <INPUT TYPE="RADIO" NAME="searchEngine"
   VALUE="lycos">
   Lycos |
   <INPUT TYPE="RADIO" NAME="searchEngine"
   VALUE="hotbot">
   HotBot
   <BR>
   <INPUT TYPE="SUBMIT" VALUE="Search">
   </CENTER>
  </FORM>
  
  </BODY>
  </HTML>
8.1 HTTP应答头概述
  
     Web服务器的HTTP应答一般由以下几项构成:一个状态行,一个或多个应答头,一个空行,内容文档。设置HTTP应答头往往和设置状态行中的状态代码结合起来。例如,有好几个表示“文档位置已经改变”的状态代码都伴随着一个Location头,而401(Unauthorized)状态代码则必须伴随一个WWW-Authenticate头。
  
     然而,即使在没有设置特殊含义的状态代码时,指定应答头也是很有用的。应答头可以用来完成:设置Cookie,指定修改日期,指示浏览器按照指定的间隔刷新页面,声明文档的长度以便利用持久HTTP连接,……等等许多其他任务。
  
     设置应答头最常用的方法是HttpServletResponse的setHeader,该方法有两个参数,分别表示应答头的名字和值。和设置状态代码相似,设置应答头应该在发送任何文档内容之前进行。
  
     setDateHeader方法和setIntHeadr方法专门用来设置包含日期和整数值的应答头,前者避免了把Java时间转换为GMT时间字符串的麻烦,后者则避免了把整数转换为字符串的麻烦。
  
     HttpServletResponse还提供了许多设置常见应答头的简便方法,如下所示:
  
  setContentType:设置Content-Type头。大多数Servlet都要用到这个方法。
  setContentLength:设置Content-Length头。对于支持持久HTTP连接的浏览器来说,这个函数是很有用的。
  addCookie:设置一个Cookie(Servlet API中没有setCookie方法,因为应答往往包含多个Set-Cookie头)。
  另外,如上节介绍,sendRedirect方法设置状态代码302时也会设置Location头。
     8.2 常见应答头及其含义
  
     有关HTTP头详细和完整的说明,请参见http://www.w3.org/Protocols/ 规范。
  
  应答头 说明
  Allow 服务器支持哪些请求方法(如GET、POST等)。
  Content-Encoding 文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此,Servlet应该通过查看Accept-Encoding头(即request.getHeader("Accept-Encoding"))检查浏览器是否支持gzip,为支持gzip的浏览器返回经gzip压缩的HTML页面,为其他浏览器返回普通页面。
  Content-Length 表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。如果你想要利用持久连接的优势,可以把输出文档写入ByteArrayOutputStram,完成后查看其大小,然后把该值放入Content-Length头,最后通过byteArrayStream.writeTo(response.getOutputStream()发送内容。
  Content-Type 表示后面的文档属于什么MIME类型。Servlet默认为text/plain,但通常需要显式地指定为text/html。由于经常要设置Content-Type,因此HttpServletResponse提供了一个专用的方法setContentTyep。
  Date 当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。
  Expires 应该在什么时候认为文档已经过期,从而不再缓存它?
  Last-Modified 文档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。Last-Modified也可用setDateHeader方法来设置。
  Location 表示客户应当到哪里去提取文档。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。
  Refresh 表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过setHeader("Refresh", "5; URL=http://host/path")让浏览器读取指定的页面。
  注意这种功能通常是通过设置HTML页面HEAD区的<META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path">实现,这是因为,自动刷新或重定向对于那些不能使用CGI或Servlet的HTML编写者十分重要。但是,对于Servlet来说,直接设置Refresh头更加方便。
  
  注意Refresh的意义是“N秒之后刷新本页面或访问指定页面”,而不是“每隔N秒刷新本页面或访问指定页面”。因此,连续刷新要求每次都发送一个Refresh头,而发送204状态代码则可以阻止浏览器继续刷新,不管是使用Refresh头还是<META HTTP-EQUIV="Refresh" ...>。
  
  注意Refresh头不属于HTTP 1.1正式规范的一部分,而是一个扩展,但Netscape和IE都支持它。
  
  Server 服务器名字。Servlet一般不设置这个值,而是由Web服务器自己设置。
  Set-Cookie 设置和页面关联的Cookie。Servlet不应使用response.setHeader("Set-Cookie", ...),而是应使用HttpServletResponse提供的专用方法addCookie。参见下文有关Cookie设置的讨论。
  WWW-Authenticate 客户应该在Authorization头中提供什么类型的授权信息?在包含401(Unauthorized)状态行的应答中这个头是必需的。例如,response.setHeader("WWW-Authenticate", "BASIC realm=\"executives\"")。
  注意Servlet一般不进行这方面的处理,而是让Web服务器的专门机制来控制受密码保护页面的访问(例如.htaccess)。
  
  
  
     8.3 实例:内容改变时自动刷新页面
  
     下面这个Servlet用来计算大素数。因为计算非常大的数字(例如500位)可能要花不少时间,所以Servlet将立即返回已经找到的结果,同时在后台继续计算。后台计算使用一个优先级较低的线程以避免过多地影响Web服务器的性能。如果计算还没有完成,Servlet通过发送Refresh头指示浏览器在几秒之后继续请求新的内容。
  
     注意,本例除了说明HTTP应答头的用处之外,还显示了Servlet的另外两个很有价值的功能。首先,它表明Servlet能够处理多个并发的连接,每个都有自己的线程。Servlet维护了一份已有素数计算请求的Vector表,通过查找素数个数(素数列表的长度)和数字个数(每个素数的长度)将当前请求和已有请求相匹配,把所有这些请求同步到这个列表上。第二,本例证明,在Servlet中维持请求之间的状态信息是非常容易的。维持状态信息在传统的CGI编程中是一件很麻烦的事情。由于维持了状态信息,浏览器能够在刷新页面时访问到正在进行的计算过程,同时也使得Servlet能够保存一个有关最近请求结果的列表,当一个新的请求指定了和最近请求相同的参数时可以立即返回结果。
  
     PrimeNumbers.java
  
     注意,该Servlet要用到前面给出的ServletUtilities.java。另外还要用到:PrimeList.java,用于在后台线程中创建一个素数的Vector;Primes.java,用于随机生成BigInteger类型的大数字,检查它们是否是素数。(此处略去PrimeList.java和Primes.java的代码。)
  package hall;
  
  import java.io.*;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.util.*;
  
  public class PrimeNumbers extends HttpServlet {
   private static Vector primeListVector = new Vector();
   private static int maxPrimeLists = 30;
  
   public void doGet(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   int numPrimes = ServletUtilities.getIntParameter(request, "numPrimes", 50);
   int numDigits = ServletUtilities.getIntParameter(request, "numDigits", 120);
   PrimeList primeList = findPrimeList(primeListVector, numPrimes, numDigits);
   if (primeList == null) {
   primeList = new PrimeList(numPrimes, numDigits, true);
   synchronized(primeListVector) {
   if (primeListVector.size() >= maxPrimeLists)
   primeListVector.removeElementAt(0);
   primeListVector.addElement(primeList);
   }
   }
   Vector currentPrimes = primeList.getPrimes();
   int numCurrentPrimes = currentPrimes.size();
   int numPrimesRemaining = (numPrimes - numCurrentPrimes);
   boolean isLastResult = (numPrimesRemaining == 0);
   if (!isLastResult) {
   response.setHeader("Refresh", "5");
   }
   response.setContentType("text/html");
   PrintWriter out = response.getWriter();
   String title = "Some " + numDigits + "-Digit Prime Numbers";
   out.println(ServletUtilities.headWithTitle(title) +
   "<BODY BGCOLOR=\"#FDF5E6\">\n" +
   "<H2 ALIGN=CENTER>" + title + "</H2>\n" +
   "<H3>Primes found with " + numDigits +
   " or more digits: " + numCurrentPrimes + ".</H3>");
   if (isLastResult)
   out.println("<B>Done searching.</B>");
   else
   out.println("<B>Still looking for " + numPrimesRemaining +
   " more<BLINK>...</BLINK></B>");
   out.println("<OL>");
   for(int i=0; i<numCurrentPrimes; i++) {
   out.println(" <LI>" + currentPrimes.elementAt(i));
   }
   out.println("</OL>");
   out.println("</BODY></HTML>");
   }
  
   public void doPost(HttpServletRequest request,
   HttpServletResponse response)
   throws ServletException, IOException {
   doGet(request, response);
   }
  
   // 检查是否存在同类型请求(已经完成,或者正在计算)。
   // 如存在,则返回现有结果而不是启动新的后台线程。
   private PrimeList findPrimeList(Vector primeListVector,
   int numPrimes,
   int numDigits) {
   synchronized(primeListVector) {
   for(int i=0; i<primeListVector.size(); i++) {
   PrimeList primes = (PrimeList)primeListVector.elementAt(i);
   if ((numPrimes == primes.numPrimes()) &&
   (numDigits == primes.numDigits()))
   return(primes);
   }
   return(null);
   }
   }
  } 


posted on 2006-09-01 17:20 阿成 阅读(611) 评论(0)  编辑  收藏 所属分类: JSP&SERVLET

只有注册用户登录后才能发表评论。


网站导航: