站内搜索

Java 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。

  • 上一篇:返回列表
  • 下一篇:JSP入门教程