`
xushaohui1
  • 浏览: 5874 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

How Tomcat Works中文版

阅读更多
    
介绍

概要
    欢迎阅读《How Tomcat Works》这本书。这本书解剖了Tomcat4.1.12和5.0.18版本,解释了它的servlet容器的内部运行机制,那是一个免费的,开源的,最受欢迎的servlet容器,代号为Catalina。Tomcat是一个复杂的系统,由许多不同的组件构成。那些想要学习Tomcat运行机制的朋友大部分知道从何入手。这本书会提供一个蓝图,然后为每一个组件构造一个简化版本,使得可以更加容易的理解这些组件。在这之后才会对真实的组件进行解释。
    你应该从这份简介开始阅读,因为它解释了这本书的结构,同时给你勾画了这个项目构造的简洁轮廓。“准备前提软件”这一节会给你一些指示,例如你需要下载什么样的软件,如何为你的代码创建目录结构等等。
本书为谁而作
    这本书是为任何一个使用Java技术进行工作的人而准备的。
假如你是一个servlet/jsp程序员或者一个Tomcat用户,而且对一个servlet容器是如何工作这个问题你感兴趣的话,这本书就是为你准备的。
假如你想加入Tomcat的开发团队的话,这本书就是为你准备的,因为你首先需要学习那些已存在的代码是如何工作的。
假如你从未涉及web开发,但你对一般意义上的软件开发感兴趣的话,你可以在这本书学到一个像Tomcat一样的大型项目是如何进行设计和开发的。
假如你想配置和自定义Tomcat,你也应该读读这本书。
    为了理解书中的讨论,你需要了解Java面向对象编程技术以及servlet编程。假如你对这些不熟悉的话,这里有很多书籍可以参考,包括Budi的《Java for the Web with Servlets, JSP, and EJB》。为了让这些材料更容易理解,每一章开始都会有便于理解所讨论主题的必要的背景资料介绍。
Servlet容器是如何工作的
    servlet容器是一个复杂的系统。不过,一个servlet容器要为一个servlet的请求提供服务,基本上有三件事要做:
创建一个request对象并填充那些有可能被所引用的servlet使用的信息,如参数、头部、cookies、查询字符串、URI等等。一个request对象是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest接口的一个实例。
创建一个response对象,所引用的servlet使用它来给客户端发送响应。一个response对象javax.servlet.ServletResponse或javax.servlet.http.ServletResponse接口的一个实例。
调用servlet的service方法,并传入request和response对象。在这里servlet会从request对象取值,给response写值。
    当你读这些章节的时候,你将会找到关于catalina servlet容器的详细讨论。
Catalina架构图
    Catalina是一个非常复杂的,并优雅的设计开发出来的软件,同时它也是模块化的。基于“Servlet容器是如何工作的”这一节中提到的任务,你可以把Catalina看成是由两个主要模块所组成的:连接器(connector)和容器(container)。在Figure I.1中的架构图,当然是简化了。在稍后的章节里边,你将会一个个的揭开所有更小的组件的神秘面纱。 

    现在重新回到Figure I.1,连接器是用来“连接”容器里边的请求的。它的工作是为接收到每一个HTTP请求构造一个request和response对象。然后它把流程传递给容器。容器从连接器接收到requset和response对象之后调用servlet的service方法用于响应。谨记,这个描述仅仅是冰山一角而已。这里容器做了相当多事情。例如,在它调用servlet的service方法之前,它必须加载这个servlet,验证用户(假如需要的话),更新用户会话等等。一个容器为了处理这个进程使用了很多不同的模块,这也并不奇怪。例如,管理模块是用来处理用户会话,而加载器是用来加载servlet类等等。
Tomcat 4和5
    这本书涵盖了Tomcat4和5.这两者有一些不同之处:
Tomcat 5支持Servlet 2.4和JSP 2.0规范,而Tomcat 4支持Servlet 2.3和JSP 1.2。
比起Tomcat 4,Tomcat 5有一些更有效率的默认连接器。
Tomcat 5共享一个后台处理线程,而Tomcat 4的组件都有属于自己的后台处理线程。因此,就这一点而言,Tomcat 5消耗较少的资源。
Tomcat 5并不需要一个映射组件(mapper component)用于查找子组件,因此简化了代码。
各章概述
    这本书共20章,其中前面两章作为导言。
    第1章说明一个HTTP服务器是如何工作的,第2章突出介绍了一个简单的servlet容器。接下来的两章关注连接器,第5章到第20章涵盖容器里边的每一个组件。以下是各章节的摘要。
    注意:对于每个章节,会有一个附带程序,类似于正在被解释的组件。
    第1章从这本书一开始就介绍了一个简单的HTTP服务器。要建立一个可工作的HTTP服务器,你需要知道在java.net包里边的2个类的内部运作:Socket和ServerSocket。这里有关于这2个类足够的背景资料,使得你能够理解附带程序是如何工作的。
    第2章说明简单的servlet容器是如何工作的。这一章带有2个servlet容器应用,可以处理静态资源和简单的servlet请求。尤其是你将会学到如何创建request和response对象,然后把它们传递给被请求的servlet的service方法。在servlet容器里边还有一个servlet,你可以从一个web浏览器中调用它。
    第3章介绍了一个简化版本的Tomcat 4默认连接器。这章里边的程序提供了一个学习工具,用于理解第4章里边的讨论的连接器。
    第4章介绍了Tomcat 4的默认连接器。这个连接器已经不推荐使用,推荐使用一个更快的连接器,Coyote。不过,默认的连接器更简单,更易于理解。
    第5章讨论container模块。container指的是org.apache.catalina.Container接口,有4种类型的container:engine, host, context和wrapper。这章提供了两个工作于context和wrapper的程序。
    第6章解释了Lifecycle接口。这个接口定义了一个Catalina组件的生命周期,并提供了一个优雅的方式,用来把在该组件发生的事件通知其他组件。另外,Lifecycle接口提供了一个优雅的机制,用于在Catalina通过单一的start/stop来启动和停止组件
    第7章包括日志,该组件是用来记录错误信息和其他信息的。
    第8章解释了加载器(loader)。加载器是一个重要的Catalina模块,负责加载servlet和一个web应用所需的其他类。这章还展示了如何实现应用的重新加载。
    第9章讨论了管理器(manager)。这个组件用来管理会话管理中的会话信息。它解释了各式各样类型的管理器,管理器是如何把会话对象持久化的。在章末,你将会学到如何创建一个的应用,该应用使用StandardManager实例来运行一个使用会话对象进行储值的servlet。
    第10章包括web应用程序安全性的限制,用来限制进入某些内容。你将会学习与安全相关的实体,例如
主角(principals),角色(roles),登陆配置,认证等等。你也将会写两个程序,它们在StandardContext对象中安装一个身份验证阀(authenticator valve)并且使用了基本的认证来对用户进行认证。
    第11章详细解释了在一个web应用中代表一个servlet的org.apache.catalina.core.StandardWrapper类。特别的是,这章解释了过滤器(filter)和一个servlet的service方法是怎样给调用的。这章的附带程序使用StandardWrapper实例来代表servlet。
    第12章包括了在一个web应用中代表一个servlet的org.apache.catalina.core.StandardContext类。特别是这章讨论了一个StandardContext对象是如何给配置的,对于每个传入的HTTP请求在它里面会发生什么,是怎样支持自动重新加载的,还有就是,在一个在其相关的组件中执行定期任务的线程中,Tomcat 5是如何共享的。
    第13章介绍了另外两个容器:host和engine。你也同样可以找到这两个容器的标准实现:org.apache.catalina.core.StandardHost和org.apache.catalina.core.StandardEngine。
    第14章提供了服务器和服务组件的部分。服务器为整个servlet容器提供了一个优雅的启动和停止机制,而服务为容器和一个或多个连接器提供了一个支架。这章附带的程序说明了如何使用服务器和服务。
    第15章解释了通过Digester来配置web应用。Digester是来源于Apache软件基金会的一个令人振奋的开源项目。对那些尚未初步了解的人,这章通过一节略微介绍了Digester库以及XML文件中如何使用它来把节点转换为Java对象。然后解释了用来配置一个StandardContext实例的ContextConfig对象。
    第16章解释了shutdown钩子,Tomcat使用它总能获得一个机会用于clean-up,而无论用户是怎样停止它的(即适当的发送一个shutdown命令或者不适当的简单关闭控制台)。
    第17章讨论了通过批处理文件和shell脚本对Tomcat进行启动和停止。
    第18章介绍了部署工具(deployer),这个组件是负责部署和安装web应用的。
    第19章讨论了一个特殊的接口,ContainerServlet,能够让servlet访问Catalina的内部对象。特别是,它讨论了Manager应用,你可以通过它来部署应用程序。
    第20章讨论了JMX以及Tomcat是如何通过为其内部对象创建MBeans使得这些对象可管理的。
各章的程序
    每一章附带了一个或者多个程序,侧重于Catalina的一个特定的组件。通常你可以找到这些简化版本,无论是正在被解释的组件或者解释如何使用Catalina组件的代码。各章节的程序的所有的类和接口都放在ex[章节号].pyrmont包或者它的子包。例如第1章的程序的类就是放在ex01.pyrmont包中。
准备的前提软件
    这本书附带的程序运行于J2SE1.4版本。压缩源文件可以从作者的网站www.brainysoftware.com中下载。它包括Tomcat 4.1.12和这本书所使用的程序的源代码。假设你已经安装了J2SE 1.4并且你的path环境变量中已经包括了JDK的安装目录,请按照下列步骤:
解压缩ZIP文件。所有的解压缩文件将放在一个新的目录howtomcatworks中。howtomcatworks将是你的工作目录。在howtomcatworks目录下面将会有数个子目录,包括lib (包括所有所需的库),src (包括所有的源文件),webroot (包括一个HTML文件和三个servlet样本),和webapps (包括示例应用程序)。
改变目录到工作目录下并编译java文件。加入你使用的是Windows,运行win-compile.bat文件。假如你的计算机是Linux机器,敲入以下内容:(如有必要的话不用忘记使用chmod更改文件属性)
        ./linux-compile.sh
    注意:你可以在ZIP文件中的Readme.txt文件找到更多信息。
第一章:一个简单的Web服务器
    本章说明java web服务器是如何工作的。Web服务器也成为超文本传输协议(HTTP)服务器,因为它使用HTTP来跟客户端进行通信的,这通常是个web浏览器。一个基于java的web服务器使用两个重要的类:java.net.Socket和java.net.ServerSocket,并通过HTTP消息进行通信。因此这章就自然是从HTTP和这两个类的讨论开始的。接下去,解释这章附带的一个简单的web服务器。
超文本传输协议(HTTP)
    HTTP是一种协议,允许web服务器和浏览器通过互联网进行来发送和接受数据。它是一种请求和响应协议。客户端请求一个文件而服务器响应请求。HTTP使用可靠的TCP连接--TCP默认使用80端口。第一个HTTP版是HTTP/0.9,然后被HTTP/1.0所替代。正在取代HTTP/1.0的是当前版本HTTP/1.1,它定义于征求意见文档(RFC) 2616,可以从http://www.w3.org/Protocols/HTTP/1.1/rfc2616.pdf下载。
    注意:本节涵盖的HTTP 1.1只是简略的帮助你理解web服务器应用发送的消息。假如你对更多详细信息感兴趣,请阅读RFC 2616。
    在HTTP中,始终都是客户端通过建立连接和发送一个HTTP请求从而开启一个事务。web服务器不需要联系客户端或者对客户端做一个回调连接。无论是客户端或者服务器都可以提前终止连接。举例来说,当你正在使用一个web浏览器的时候,可以通过点击浏览器上的停止按钮来停止一个文件的下载进程,从而有效的关闭与web服务器的HTTP连接。
HTTP请求
    一个HTTP请求包括三个组成部分:
方法—统一资源标识符(URI)—协议/版本
请求的头部
主体内容
    下面是一个HTTP请求的例子:
POST /examples/default.jsp HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate

lastName=Franks&firstName=Michael
    方法—统一资源标识符(URI)—协议/版本出现在请求的第一行。 
POST /examples/default.jsp HTTP/1.1
    这里POST是请求方法,/examples/default.jsp是URI,而HTTP/1.1是协议/版本部分。
    每个HTTP请求可以使用HTTP标准里边提到的多种方法之一。HTTP 1.1支持7种类型的请求:GET, POST,
HEAD, OPTIONS, PUT, DELETE和TRACE。GET和POST在互联网应用里边最普遍使用的。
    URI完全指明了一个互联网资源。URI通常是相对服务器的根目录解释的。因此,始终一斜线/开头。统一资源定位器(URL)其实是一种URI(查看http://www.ietf.org/rfc/rfc2396.txt)来的。该协议版本代表了正在使用的HTTP协议的版本。
    请求的头部包含了关于客户端环境和请求的主体内容的有用信息。例如它可能包括浏览器设置的语言,主体内容的长度等等。每个头部通过一个回车换行符(CRLF)来分隔的。
    对于HTTP请求格式来说,头部和主体内容之间有一个回车换行符(CRLF)是相当重要的。CRLF告诉HTTP服务器主体内容是在什么地方开始的。在一些互联网编程书籍中,CRLF还被认为是HTTP请求的第四部分。
    在前面一个HTTP请求中,主体内容只不过是下面一行:

lastName=Franks&firstName=Michael

    实体内容在一个典型的HTTP请求中可以很容易的变得更长。

HTTP响应
    类似于HTTP请求,一个HTTP响应也包括三个组成部分:
方法—统一资源标识符(URI)—协议/版本
响应的头部
主体内容
    下面是一个HTTP响应的例子:
HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Date: Mon, 5 Jan 2004 13:13:33 GMT
Content-Type: text/html
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT
Content-Length: 112

<html>
<head>
<title>HTTP Response Example</title>
</head>
<body>
Welcome to Brainy Software
</body>
</html>

    响应头部的第一行类似于请求头部的第一行。第一行告诉你该协议使用HTTP 1.1,请求成功(200=成功),表示一切都运行良好。
    响应头部和请求头部类似,也包括很多有用的信息。响应的主体内容是响应本身的HTML内容。头部和主体内容通过CRLF分隔开来。

Socket类
    套接字是网络连接的一个端点。套接字使得一个应用可以从网络中读取和写入数据。放在两个不同计算机上的两个应用可以通过连接发送和接受字节流。为了从你的应用发送一条信息到另一个应用,你需要知道另一个应用的IP地址和套接字端口。在Java里边,套接字指的是java.net.Socket类。
    要创建一个套接字,你可以使用Socket类众多构造方法中的一个。其中一个接收主机名称和端口号:

public Socket (java.lang.String host, int port)

    在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号。例如,要连接yahoo.com的80端口,你需要构造以下的Socket对象:

new Socket ("yahoo.com", 80);

    一旦你成功创建了一个Socket类的实例,你可以使用它来发送和接受字节流。要发送字节流,你首先必须调用Socket类的getOutputStream方法来获取一个java.io.OutputStream对象。要发送文本到一个远程应用,你经常要从返回的OutputStream对象中构造一个java.io.PrintWriter对象。要从连接的另一端接受字节流,你可以调用Socket类的getInputStream方法用来返回一个java.io.InputStream对象。
    以下的代码片段创建了一个套接字,可以和本地HTTP服务器(127.0.0.1是指本地主机)进行通讯,发送一个HTTP请求,并从服务器接受响应。它创建了一个StringBuffer对象来保存响应并在控制台上打印出来。

Socket socket = new Socket("127.0.0.1", "8080");
OutputStream os = socket.getOutputStream();
boolean autoflush = true;
PrintWriter out = new PrintWriter(
socket.getOutputStream(), autoflush);
BufferedReader in = new BufferedReader(
new InputStreamReader( socket.getInputstream() ));
// send an HTTP request to the web server
out.println("GET /index.jsp HTTP/1.1");
out.println("Host: localhost:8080");
out.println("Connection: Close");
out.println();
// read the response
boolean loop = true;
StringBuffer sb = new StringBuffer(8096);
while (loop) {
    if ( in.ready() ) {
        int i=0;
        while (i!=-1) {
            i = in.read();
            sb.append((char) i);
        }
    loop = false;
    }
    Thread.currentThread().sleep(50);
}
// display the response to the out console
System.out.println(sb.toString());
socket.close();

    请注意,为了从web服务器获取适当的响应,你需要发送一个遵守HTTP协议的HTTP请求。假如你已经阅读了前面一节超文本传输协议(HTTP),你应该能够理解上面代码提到的HTTP请求。
    注意:你可以本书附带的com.brainysoftware.pyrmont.util.HttpSniffer类来发送一个HTTP请求并显示响应。要使用这个Java程序,你必须连接到互联网上。虽然它有可能并不会起作用,假如你有设置防火墙的话。

ServerSocket类
    Socket类代表一个客户端套接字,即任何时候你想连接到一个远程服务器应用的时候你构造的套接字,现在,假如你想实施一个服务器应用,例如一个HTTP服务器或者FTP服务器,你需要一种不同的做法。这是因为你的服务器必须随时待命,因为它不知道一个客户端应用什么时候会尝试去连接它。为了让你的应用能随时待命,你需要使用java.net.ServerSocket类。这是服务器套接字的实现。
    ServerSocket和Socket不同,服务器套接字的角色是等待来自客户端的连接请求。一旦服务器套接字获得一个连接请求,它创建一个Socket实例来与客户端进行通信。
    要创建一个服务器套接字,你需要使用ServerSocket类提供的四个构造方法中的一个。你需要指定IP地址和服务器套接字将要进行监听的端口号。通常,IP地址将会是127.0.0.1,也就是说,服务器套接字将会监听本地机器。服务器套接字正在监听的IP地址被称为是绑定地址。服务器套接字的另一个重要的属性是backlog,这是服务器套接字开始拒绝传入的请求之前,传入的连接请求的最大队列长度。
    其中一个ServerSocket类的构造方法如下所示:

public ServerSocket(int port, int backLog, InetAddress bindingAddress);

    对于这个构造方法,绑定地址必须是java.net.InetAddress的一个实例。一种构造InetAddress对象的简单的方法是调用它的静态方法getByName,传入一个包含主机名称的字符串,就像下面的代码一样。

InetAddress.getByName("127.0.0.1");

    下面一行代码构造了一个监听的本地机器8080端口的ServerSocket,它的backlog为1。

new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));

    一旦你有一个ServerSocket实例,你可以让它在绑定地址和服务器套接字正在监听的端口上等待传入的连接请求。你可以通过调用ServerSocket类的accept方法做到这点。这个方法只会在有连接请求时才会返回,并且返回值是一个Socket类的实例。Socket对象接下去可以发送字节流并从客户端应用中接受字节流,就像前一节"Socket类"解释的那样。实际上,这章附带的程序中,accept方法是唯一用到的方法。

应用程序
    我们的web服务器应用程序放在ex01.pyrmont包里边,由三个类组成:

HttpServer
Request
Response
    这个应用程序的入口点(静态main方法)可以在HttpServer类里边找到。main方法创建了一个HttpServer的实例并调用了它的await方法。await方法,顾名思义就是在一个指定的端口上等待HTTP请求,处理它们并发送响应返回客户端。它一直等待直至接收到shutdown命令。
    应用程序不能做什么,除了发送静态资源,例如放在一个特定目录的HTML文件和图像文件。它也在控制台上显示传入的HTTP请求的字节流。不过,它不给浏览器发送任何的头部例如日期或者cookies。
    现在我们将在以下各小节中看看这三个类。

HttpServer类
    HttpServer类代表一个web服务器并展示在Listing 1.1中。请注意,await方法放在Listing 1.2中,为了节省空间没有重复放在Listing 1.1中。
        Listing 1.1: HttpServer类

package ex01.pyrmont;
import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.File;
public class HttpServer {
    /** WEB_ROOT is the directory where our HTML and other files reside.
    * For this package, WEB_ROOT is the "webroot" directory under the
    * working directory.
    * The working directory is the location in the file system
    * from where the java command was invoked.
    */
    public static final String WEB_ROOT =
    System.getProperty("user.dir") + File.separator + "webroot";
    // shutdown command
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
    // the shutdown command received
    private boolean shutdown = false;
    public static void main(String[] args) {
        HttpServer server = new HttpServer();
        server.await();
    }
    public void await() {
        ...
    }
}

        Listing 1.2: HttpServer类的await方法
public void await() {
    ServerSocket serverSocket = null;
    int port = 8080;
    try {
        serverSocket = new ServerSocket(port, 1,
        InetAddress.getByName("127.0.0.1"));
    }
    catch (IOException e) {
    e.printStackTrace();
    System.exit(1);
    }
    // Loop waiting for a request
    while (!shutdown) {
        Socket socket = null;
        InputStream input = null;
        OutputStream output = null;
        try {
            socket = serverSocket.accept();
            input = socket.getInputStream();
            output = socket.getOutputStream();
            // create Request object and parse
            Request request = new Request(input);
            request.parse();
            // create Response object
            Response response = new Response(output);
            response.setRequest(request);
            response.sendStaticResource();
            // Close the socket
            socket.close();
            //check if the previous URI is a shutdown command
            shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
        }
        catch (Exception e) {
            e.printStackTrace ();
            continue;
        }
    }
}

     web服务器能提供公共静态final变量WEB_ROOT所在的目录和它下面所有的子目录下的静态资源。如下所示,WEB_ROOT被初始化:

public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";

    代码列表包括一个叫webroot的目录,包含了一些你可以用来测试这个应用程序的静态资源。你同样可以在相同的目录下找到几个servlet用于测试下一章的应用程序。为了请求一个静态资源,在你的浏览器的地址栏或者网址框里边敲入以下的URL:

http://machineName:port/staticResource

    如果你要从一个不同的机器上发送请求到你的应用程序正在运行的机器上,machineName应该是正在运行应用程序的机器的名称或者IP地址。假如你的浏览器在同一台机器上,你可以使用localhost作为machineName。端口是8080,staticResource是你需要请求的文件的名称,且必须位于WEB_ROOT里边。
    举例来说,假如你正在使用同一台计算机上测试应用程序,并且你想要调用HttpServer对象去发送一个index.html文件,你可以使用一下的URL:

http://localhost:8080/index.html

    要停止服务器,你可以在web浏览器的地址栏或者网址框里边敲入预定义字符串,就在URL的host:port的后面,发送一个shutdown命令。shutdown命令是在HttpServer类的静态final变量SHUTDOWN里边定义的:

private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

    因此,要停止服务器,使用下面的URL:

http://localhost:8080/SHUTDOWN

    现在我们来看看Listing 1.2印出来的await方法。
    使用方法名await而不是wait是因为wait方法是与线程相关的java.lang.Object类的一个重要方法。
    await方法首先创建一个ServerSocket实例然后进入一个while循环。

serverSocket = new ServerSocket(port, 1,
    InetAddress.getByName("127.0.0.1"));
...
// Loop waiting for a request
while (!shutdown) {
    ...
}

    while循环里边的代码运行到ServletSocket的accept方法停了下来,只会在8080端口接收到一个HTTP请求的时候才返回:

socket = serverSocket.accept();

    接收到请求之后,await方法从accept方法返回的Socket实例中取得java.io.InputStream和java.io.OutputStream对象。

input = socket.getInputStream();
output = socket.getOutputStream();

    await方法接下去创建一个ex01.pyrmont.Request对象并且调用它的parse方法去解析HTTP请求的原始数据。
// create Request object and parse
Request request = new Request(input);
request.parse ();

    在这之后,await方法创建一个Response对象,把Request对象设置给它,并调用它的sendStaticResource方法。

// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();

    最后,await关闭套接字并调用Request的getUri来检测HTTP请求的URI是不是一个shutdown命令。假如是的话,shutdown变量将被设置为true且程序会退出while循环。

// Close the socket
socket.close ();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);

Request类

    ex01.pyrmont.Request类代表一个HTTP请求。从负责与客户端通信的Socket中传递过来InputStream对象来构造这个类的一个实例。你调用InputStream对象其中一个read方法来获取HTTP请求的原始数据。
    Request类显示在Listing 1.3。Request对象有parse和getUri两个公共方法,分别在Listings 1.4和1.5列出来。
        Listing 1.3: Request类

package ex01.pyrmont;
import java.io.InputStream;
import java.io.IOException;
public class Request {
    private InputStream input;
    private String uri;
    public Request(InputStream input) {
        this.input = input;
    }
    public void parse() {
        ...
    }
    private String parseUri(String requestString) {
        ...
    }
    public String getUri() {
        return uri;
    }
}

        Listing 1.4: Request类的parse方法
public void parse() {
    // Read a set of characters from the socket
    StringBuffer request = new StringBuffer(2048);
    int i;
    byte[] buffer = new byte[2048];
    try {
        i = input.read(buffer);
    }
    catch (IOException e) {
        e.printStackTrace();
        i = -1;
    }
    for (int j=0; j<i; j++) {
        request.append((char) buffer[j]);
    }
    System.out.print(request.toString());
    uri = parseUri(request.toString());
}

        Listing 1.5: Request类的parseUri方法

private String parseUri(String requestString) {
    int index1, index2;
    index1 = requestString.indexOf(' ');
    if (index1 != -1) {
        index2 = requestString.indexOf(' ', index1 + 1);
        if (index2 > index1)
        return requestString.substring(index1 + 1, index2);
    }
    return null;
}

    parse方法解析HTTP请求里边的原始数据。这个方法没有做很多事情。它唯一可用的信息是通过调用HTTP请求的私有方法parseUri获得的URI。parseUri方法在uri变量里边存储URI。公共方法getUri被调用并返回HTTP请求的URI。
    注意:在第3章和下面各章的附带程序里边,HTTP请求将会对原始数据进行更多的处理。
    为了理解parse和parseUri方法是怎样工作的,你需要知道上一节“超文本传输协议(HTTP)”讨论的HTTP请求的结构。在这一章中,我们仅仅关注HTTP请求的第一部分,请求行。请求行从一个方法标记开始,接下去是请求的URI和协议版本,最后是用回车换行符(CRLF)结束。请求行里边的元素是通过一个空格来分隔的。例如,使用GET方法来请求index.html文件的请求行如下所示。

GET /index.html HTTP/1.1

    parse方法从传递给Requst对象的套接字的InputStream中读取整个字节流并在一个缓冲区中存储字节数组。然后它使用缓冲区字节数据的字节来填入一个StringBuffer对象,并且把代表StringBuffer的字符串传递给parseUri方法。
    parse方法列在Listing 1.4。
    然后parseUri方法从请求行里边获得URI。Listing 1.5给出了parseUri方法。parseUri方法搜索请求里边的第一个和第二个空格并从中获取URI。

Response类
    ex01.pyrmont.Response类代表一个HTTP响应,在Listing 1.6里边给出。
        Listing 1.6: Response类

package ex01.pyrmont;
import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.File;
/*
HTTP Response = Status-Line
*(( general-header | response-header | entity-header ) CRLF)
CRLF
[ message-body ]
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
*/
public class Response {
    private static final int BUFFER_SIZE = 1024;
    Request request;
    OutputStream output;
    public Response(OutputStream output) {
        this.output = output;
    }
    public void setRequest(Request request) {
        this.request = request;
    }
    public void sendStaticResource() throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputStream fis = null;
        try {
            File file = new File(HttpServer.WEB_ROOT, request.getUri());
            if (file.exists()) {
                fis = new FileInputStream(file);
                int ch = fis.read(bytes, 0, BUFFER_SIZE);
                while (ch!=-1) {
                    output.write(bytes, 0, ch);
                    ch = fis.read(bytes, 0, BUFFER_SIZE);
                }
            }
            else {
            // file not found
            String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
                "Content-Type: text/html\r\n" +
                "Content-Length: 23\r\n" +
                "\r\n" +
                "<h1>File Not Found</h1>";
            output.write(errorMessage.getBytes());
            }
        }
        catch (Exception e) {
            // thrown if cannot instantiate a File object
            System.out.println(e.toString() );
        }
        finally {
            if (fis!=null)
            fis.close();
        }
    }
}

    首先注意到它的构造方法接收一个java.io.OutputStream对象,就像如下所示。

public Response(OutputStream output) {
    this.output = output;
}

    响应对象是通过传递由套接字获得的OutputStream对象给HttpServer类的await方法来构造的。Response类有两个公共方法:setRequest和sendStaticResource。setRequest方法用来传递一个Request对象给Response对象。
    sendStaticResource方法是用来发送一个静态资源,例如一个HTML文件。它首先通过传递上一级目录的路径和子路径给File累的构造方法来实例化java.io.File类。

File file = new File(HttpServer.WEB_ROOT, request.getUri());

    然后它检查该文件是否存在。假如存在的话,通过传递File对象让sendStaticResource构造一个java.io.FileInputStream对象。然后,它调用FileInputStream的read方法并把字节数组写入OutputStream对象。请注意,这种情况下,静态资源是作为原始数据发送给浏览器的。

if (file.exists()) {
    fis = new FileInputstream(file);
    int ch = fis.read(bytes, 0, BUFFER_SIZE);
    while (ch!=-1) {
        output.write(bytes, 0, ch);
        ch = fis.read(bytes, 0, BUFFER_SIZE);
    }
}

    假如文件并不存在,sendStaticResource方法发送一个错误信息到浏览器。

String errorMessage =
    "Content-Type: text/html\r\n" +
    "Content-Length: 23\r\n" +
    "\r\n" +
    "<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());

运行应用程序
    为了运行应用程序,可以在工作目录下敲入下面的命令:

java ex01.pyrmont.HttpServer

    为了测试应用程序,可以打开你的浏览器并在地址栏或网址框中敲入下面的命令:

http://localhost:8080/index.html

    正如Figure 1.1所示,你将会在你的浏览器里边看到index.html页面。


Figure 1.1: web服务器的输出
    在控制台中,你可以看到类似于下面的HTTP请求:

GET /index.html HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg,
application/vnd.ms-excel, application/msword, application/vnd.ms-
powerpoint, application/x-shockwave-flash, application/pdf, */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR
1.1.4322)
Host: localhost:8080
Connection: Keep-Alive

GET /images/logo.gif HTTP/1.1
Accept: */*
Referer: http://localhost:8080/index.html
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR
1.1.4322)
Host: localhost:8080
Connection: Keep-Alive

总结

    在这章中你已经看到一个简单的web服务器是如何工作的。这章附带的程序仅仅由三个类组成,并不是全功能的。不过,它提供了一个良好的学习工具。下一章将要讨论动态内容的处理过程。

第2章:一个简单的Servlet容器
概要
    本章通过两个程序来说明你如何开发自己的servlet容器。第一个程序被设计得足够简单使得你能理解一个servlet容器是如何工作的。然后它演变为第二个稍微复杂的servlet容器。
    注意:每一个servlet容器的应用程序都是从前一章的应用程序逐渐演变过来的,直至一个全功能的Tomcat servlet容器在第17章被建立起来。
    这两个servlet容器都可以处理简单的servlet和静态资源。你可以使用PrimitiveServlet来测试这个容器。PrimitiveServlet在Listing 2.1中列出并且它的类文件可以在webroot目录下找到。更复杂的servlet就超过这些容器的能力了,但是你将会在以下各章中学到如何建立更复杂的servlet容器。
        Listing 2.1: PrimitiveServlet.java

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class PrimitiveServlet implements Servlet {
    public void init(ServletConfig config) throws ServletException {
    System.out.println("init");
    }
    public void service(ServletRequest request, ServletResponse response)
        throws ServletException, IOException {
        System.out.println("from service");
        PrintWriter out = response.getWriter();
        out.println("Hello. Roses are red.");
        out.print("Violets are blue.");
    }
    public void destroy() {
        System.out.println("destroy");
    }
    public String getServletInfo() {
        return null;
    }
    public ServletConfig getServletConfig() {
        return null;
    }
}

    两个应用程序的类都放在ex02.pyrmont包里边。为了理解应用程序是如何工作的,你需要熟悉javax.servlet.Servlet接口。为了给你复习一下,将会在本章的首节讨论这个接口。在这之后,你将会学习一个servlet容器做了什么工作来为一个servlet提供HTTP请求。

javax.servlet.Servlet接口
    Servlet编程是通过javax.servlet和javax.servlet.http这两个包的类和接口来实现的。其中一个至关重要的就是javax.servlet.Servlet接口了。所有的servlet必须实现实现或者继承实现该接口的类。
    Servlet接口有五个方法,其用法如下。

public void init(ServletConfig config) throws ServletException
public void service(ServletRequest request, ServletResponse response)
    throws ServletException, java.io.IOException
public void destroy()
public ServletConfig getServletConfig()
public java.lang.String getServletInfo()

    在Servlet的五个方法中,init,service和destroy是servlet的生命周期方法。在servlet类已经初始化之后,init方法将会被servlet容器所调用。servlet容器只调用一次,以此表明servlet已经被加载进服务中。init方法必须在servlet可以接受任何请求之前成功运行完毕。一个servlet程序员可以通过覆盖这个方法来写那些仅仅只要运行一次的初始化代码,例如加载数据库驱动,值初始化等等。在其他情况下,这个方法通常是留空的。
    servlet容器为servlet请求调用它的service方法。servlet容器传递一个javax.servlet.ServletRequest对象和javax.servlet.ServletResponse对象。ServletRequest对象包括客户端的HTTP请求信息,而ServletResponse对象封装servlet的响应。在servlet的生命周期中,service方法将会给调用多次。
    当从服务中移除一个servlet实例的时候,servlet容器调用destroy方法。这通常发生在servlet容器正在被关闭或者servlet容器需要一些空闲内存的时候。仅仅在所有servlet线程的service方法已经退出或者超时淘汰的时候,这个方法才被调用。在servlet容器已经调用完destroy方法之后,在同一个servlet里边将不会再调用service方法。destroy方法提供了一个机会来清理任何已经被占用的资源,例如内存,文件句柄和线程,并确保任何持久化状态和servlet的内存当前状态是同步的。
    Listing 2.1介绍了一个名为PrimitiveServlet的servlet的代码,是一个非常简单的的servlet,你可以用来测试本章里边的servlet容器应用程序。PrimitiveServlet类实现了javax.servlet.Servlet(所有的servlet都必须这样做),并为Servlet的这五个方法都提供了实现。PrimitiveServlet做的事情非常简单。在init,service或者destroy中的任何一个方法每次被调用的时候,servlet把方法名写到标准控制台上面去。另外,service方法从ServletResponse对象获得java.io.PrintWriter实例,并发送字符串到浏览器去。

应用程序1
    现在,让我们从一个servlet容器的角度来研究一下servlet编程。总的来说,一个全功能的servlet容器会为servlet的每个HTTP请求做下面一些工作:

当第一次调用servlet的时候,加载该servlet类并调用servlet的init方法(仅仅一次)。
对每次请求,构造一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。
调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。
当servlet类被关闭的时候,调用servlet的destroy方法并卸载servlet类。
    本章的第一个servlet容器不是全功能的。因此,她不能运行什么除了非常简单的servlet,而且也不调用servlet的init方法和destroy方法。相反它做了下面的事情:

等待HTTP请求。
构造一个ServletRequest对象和一个ServletResponse对象。
假如该请求需要一个静态资源的话,调用StaticResourceProcessor实例的process方法,同时传递ServletRequest和ServletResponse对象。
假如该请求需要一个servlet的话,加载servlet类并调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。
    注意:在这个servlet容器中,每一次servlet被请求的时候,servlet类都会被加载。
    第一个应用程序由6个类组成:

HttpServer1
Request
Response
StaticResourceProcessor
ServletProcessor1
Constants
    Figure 2.1显示了第一个servlet容器的UML图。


    Figure 2.1: 第一个servlet容器的UML图
    这个应用程序的入口点(静态main方法)可以在HttpServer1类里边找到。main方法创建了一个HttpServer1的实例并调用了它的await方法。await方法等待HTTP请求,为每次请求创建一个Request对象和一个Response对象,并把他们分发到一个StaticResourceProcessor实例或者一个ServletProcessor实例中去,这取决于请求一个静态资源还是一个servlet。
    Constants类包括涉及其他类的静态final变量WEB_ROOT。WEB_ROOT显示了PrimitiveServlet和这个容器可以提供的静态资源的位置。
    HttpServer1实例会一直等待HTTP请求,直到接收到一个shutdown的命令。你科研用第1章的做法发送一个shutdown命令。
    应用程序里边的每个类都会在以下各节中进行讨论。

HttpServer1类
    这个应用程序里边的HttpServer1类类似于第1章里边的简单服务器应用程序的HttpServer类。不过,在这个应用程序里边HttpServer1类可以同时提供静态资源和servlet。要请求一个静态资源,你可以在你的浏览器地址栏或者网址框里边敲入一个URL:

http://machineName:port/staticResource

    就像是在第1章提到的,你可以请求一个静态资源。
    为了请求一个servlet,你可以使用下面的URL:

http://machineName:port/servlet/servletClass

    因此,假如你在本地请求一个名为PrimitiveServlet的servlet,你在浏览器的地址栏或者网址框中敲入:

http://localhost:8080/servlet/PrimitiveServlet

    servlet容器可以就提供PrimitiveServlet了。不过,假如你调用其他servlet,如ModernServlet,servlet容器将会抛出一个异常。在以下各章中,你将会建立可以处理这两个情况的程序。
    HttpServer1类显示在Listing 2.2中。
        Listing 2.2: HttpServer1类的await方法
package ex02.pyrmont;
import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
public class HttpServer1 {
    /** WEB_ROOT is the directory where our HTML and other files reside.
    * For this package, WEB_ROOT is the "webroot" directory under the
    * working directory.
    * The working directory is the location in the file system
    * from where the java command was invoked.
    */
    // shutdown command
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
    // the shutdown command received
    private boolean shutdown = false;
    public static void main(String[] args) {
        HttpServer1 server = new HttpServer1();
        server.await();
    }
    public void await() {
        ServerSocket serverSocket = null;
        int port = 8080;
        try {
            serverSocket = new ServerSocket(port, 1,
            InetAddress.getByName("127.0.0.1"));
        }
        catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        // Loop waiting for a request
        while (!shutdown) {
            Socket socket = null;
            InputStream input = null;
            OutputStream output = null;
            try {
                socket = serverSocket.accept();
                input = socket.getInputstream();
                output = socket.getOutputStream();
                // create Request object and parse
                Request request = new Request(input);
                request.parse();
                // create Response object
                Response response = new Response(output);
                response.setRequest(request);
                // check if this is a request for a servlet or
                // a static resource
                // a request for a servlet begins with "/servlet/"
                if (request.getUri().startsWith("/servlet/")) {
                    ServletProcessor1 processor = new ServletProcessor1();
                    processor.process(request, response);
                }
                else {
                    StaticResoureProcessor processor =
                    new StaticResourceProcessor();
                    processor.process(request, response);
                }
                // Close the socket
                socket.close();
                //check if the previous URI is a shutdown command
                shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
            }
            catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    }
}

    类的await方法等待HTTP请求直到一个shutdown命令给发出,让你想起第1章的await方法。Listing 2.2的await方法和第1章的区别是,在Listing 2.2里边,请求可以分发给一个StaticResourceProcessor或者一个ServletProcessor。假如URI包括字符串/servlet/的话,请求将会转发到后面去。
    不然的话,请求将会传递给StaticResourceProcessor实例 instance. 请注意,这部分在Listing 2.2中灰暗显示。

Request类
    servlet的service方法从servlet容器中接收一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。这就是说对于每一个HTTP请求,servlet容器必须构造一个ServletRequest对象和一个ServletResponse对象并把它们传递给正在服务的servlet的service方法。
    ex02.pyrmont.Request类代表一个request对象并被传递给servlet的service方法。就本身而言,它必须实现javax.servlet.ServletRequest接口。这个类必须提供这个接口所有方法的实现。不过,我们想要让它非常简单并且仅仅提供实现其中一些方法,我们在以下各章中再实现全部的方法。要编译Request类,你需要把这些方法的实现留空。假如你看过Listing 2.3中的Request类,你将会看到那些需要返回一个对象的方法返回了null
        Listing 2.3: Request类

package ex02.pyrmont;
import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
public class Request implements ServletRequest {
    private InputStream input;
    private String uri;
    public Request(InputStream input){
        this.input = input;
    }
    public String getUri() {
        return uri;
    }
    private String parseUri(String requestString) {
        int index1, index2;
        index1 = requestString.indexOf(' ');
        if (index1 != -1) {
            index2 = requestString.indexOf(' ', index1 + 1);
            if (index2 > index1)
                return requestString.substring(index1 + 1, index2);
        }
        return null;
    }
    public void parse() {
        // Read a set of characters from the socket
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
            i = input.read(buffer);
        }
        catch (IOException e) {
            e.printStackTrace();
            i = -1;
        }
        for (int j=0; j<i; j++) {
            request.append((char) buffer(j));
        }
        System.out.print(request.toString());
        uri = parseUri(request.toString());
    }
    /* implementation of ServletRequest */
    public Object getAttribute(String attribute) {
        return null;
    }
    public Enumeration getAttributeNames() {
        return null;
    }
    public String getRealPath(String path) {
        return null;
    }
    public RequestDispatcher getRequestDispatcher(String path) {
        return null;
    }
    public boolean isSecure() {
        return false;
    }
    public String getCharacterEncoding() {
        return null;
    }
    public int getContentLength() {
        return 0;
    }
    public String getContentType() {
        return null;
    }
    public ServletInputStream getInputStream() throws IOException {
        return null;
    }
    public Locale getLocale() {
        return null;
    }
    public Enumeration getLocales() {
        return null;
    }
    public String getParameter(String name) {
        return null;
    }
    public Map getParameterMap() {
        return null;
    }
    public Enumeration getParameterNames() {
        return null;
    }
    public String[] getParameterValues(String parameter) {
        return null;
    }
    public String getProtocol() {
        return null;
    }
    public BufferedReader getReader() throws IOException {
        return null;
    }
    public String getRemoteAddr() {
        return null;
    }
    public String getRemoteHost() {
        return null;
    }
    public String getScheme() {
        return null;
    }
    public String getServerName() {
        return null;
    }
    public int getServerPort() {
        return 0;
    }
    public void removeAttribute(String attribute) { }
    public void setAttribute(String key, Object value) { }
    public void setCharacterEncoding(String encoding)
        throws UnsupportedEncodingException { }
}

    另外,Request类仍然有在第1章中讨论的parse和getUri方法。

Response类
    在Listing 2.4列出的ex02.pyrmont.Response类,实现了javax.servlet.ServletResponse。就本身而言,这个类必须提供接口里边的所有方法的实现。类似于Request类,我们把除了getWriter之外的所有方法的实现留空。
        Listing 2.4: Response类
package ex02.pyrmont;
import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.File;
import java.io.PrintWriter;
import java.util.Locale;
import javax.servlet.ServletResponse;
import javax.servlet.ServletOutputStream;
public class Response implements ServletResponse {
    private static final int BUFFER_SIZE = 1024;
    Request request;
    OutputStream output;
    PrintWriter writer;
    public Response(OutputStream output) {
        this.output = output;
    }
    public void setRequest(Request request) {
        this.request = request;
    }
    /* This method is used to serve static pages */
    public void sendStaticResource() throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputstream fis = null;
        try {
            /* request.getUri has been replaced by request.getRequestURI */
            File file = new File(Constants.WEB_ROOT, request.getUri());
            fis = new FileInputstream(file);
            /*
            HTTP Response = Status-Line
            *(( general-header | response-header | entity-header ) CRLF)
            CRLF
            [ message-body ]
            Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
            */
            int ch = fis.read(bytes, 0, BUFFER_SIZE);
            while (ch!=-1) {
                output.write(bytes, 0, ch);
                ch = fis.read(bytes, 0, BUFFER_SIZE);
            }
        }
        catch (FileNotFoundException e) {
            String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
                "Content-Type: text/html\r\n" +
                "Content-Length: 23\r\n" +
                "\r\n" +
                "<h1>File Not Found</h1>";
            output.write(errorMessage.getBytes());
        }
        finally {
            if (fis!=null)
            fis.close();
        }
    }
    /** implementation of ServletResponse */
    public void flushBuffer() throws IOException ( }
    public int getBufferSize() {
        return 0;
    }
    public String getCharacterEncoding() {
        return null;
    }
    public Locale getLocale() {
        return null;
    }
    public ServletOutputStream getOutputStream() throws IOException {
        return null;
    }
    public PrintWriter getWriter() throws IOException {
        // autoflush is true, println() will flush,
        // but print() will not.
        writer = new PrintWriter(output, true);
        return writer;
    }
    public boolean isCommitted() {
        return false;
    }
    public void reset() { }
    public void resetBuffer() { }
    public void setBufferSize(int size) { }
    public void setContentLength(int length) { }
    public void setContentType(String type) { }
    public void setLocale(Locale locale) { }
}

    在getWriter方法中,PrintWriter类的构造方法的第二个参数是一个布尔值表明是否允许自动刷新。传递true作为第二个参数将会使任何println方法的调用都会刷新输出(output)。不过,print方法不会刷新输出。
    因此,任何print方法的调用都会发生在servlet的service方法的最后一行,输出将不会被发送到浏览器。这个缺点将会在下一个应用程序中修复。
    Response类还拥有在第1章中谈到的sendStaticResource方法。

StaticResourceProcessor类

    ex02.pyrmont.StaticResourceProcessor类用来提供静态资源请求。唯一的方法是process方法。Listing 2.5给出了StaticResourceProcessor类。
        Listing 2.5: StaticResourceProcessor类

package ex02.pyrmont;
import java.io.IOException;
public class StaticResourceProcessor {
    public void process(Request request, Response response) {
    try {
        response.sendStaticResource();
    }
    catch (IOException e) {
        e.printStackTrace();
    }
    }
}

    process方法接收两个参数:一个ex02.pyrmont.Request实例和一个ex02.pyrmont.Response实例。这个方法只是简单的呼叫Response对象的sendStaticResource方法。

ServletProcessor1类
    Listing 2.6中的ex02.pyrmont.ServletProcessor1类用于处理servlet的HTTP请求。
        Listing 2.6: ServletProcessor1类

package ex02.pyrmont;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.File;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class ServletProcessor1 {
    public void process(Request request, Response response) {
        String uri = request.getUri();
        String servletName = uri.substring(uri.lastIndexOf("/") + 1);
        URLClassLoader loader = null;
        try {
            // create a URLClassLoader
            URL[] urls = new URL[1];
            URLStreamHandler streamHandler = null;
            File classPath = new File(Constants.WEB_ROOT);
            // the forming of repository is taken from the
            // createClassLoader method in
            // org.apache.catalina.startup.ClassLoaderFactory
            String repository =(new URL("file", null, classPath.getCanonicalPath() +
                File.separator)).toString() ;
            // the code for forming the URL is taken from
            // the addRepository method in
            // org.apache.catalina.loader.StandardClassLoader.
            urls[0] = new URL(null, repository, streamHandler);
            loader = new URLClassLoader(urls);
        }
        catch (IOException e) {
            System.out.println(e.toString() );
        }
        Class myClass = null;
        try {
            myClass = loader.loadClass(servletName);
        }
        catch (ClassNotFoundException e) {
            System.out.println(e.toString());
        }
        Servlet servlet = null;
        try {
            servlet = (Servlet) myClass.newInstance();
            servlet.service((ServletRequest) request,
            (ServletResponse) response);
        }
        catch (Exception e) {
            System.out.println(e.toString());
        }
        catch (Throwable e) {
            System.out.println(e.toString());
        }
    }
}

    ServletProcessor1类出奇的简单,仅仅由一个方法组成:process。这个方法接受两个参数:一个
javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。该方法从ServletRequest中通过调用getRequestUri方法获得URI:

String uri = request.getUri();

    请记住URI是以下形式的:

/servlet/servletName

    在这里servletName是servlet类的名字。
    要加载servlet类,我们需要从URI中知道servlet的名称。我们可以使用process方法的下一行来获得servlet的名字:

String servletName = uri.substring(uri.lastIndexOf("/") + 1);

    接下去,process方法加载servlet。要完成这个,你需要创建一个类加载器并告诉这个类加载器要加载的类的位置。对于这个servlet容器,类加载器直接在Constants指向的目录里边查找。WEB_ROOT就是指向工作目录下面的webroot目录。
    注意: 类加载器将在第8章详细讨论。
    要加载servlet,你可以使用java.net.URLClassLoader类,它是java.lang.ClassLoader类的一个直接子类。一旦你拥有一个URLClassLoader实例,你使用它的loadClass方法去加载一个servlet类。现在举例说明URLClassLoader类是straightforward直接转发的。这个类有三个构造方法,其中最简单的是:

public URLClassLoader(URL[] urls);

    这里urls是一个java.net.URL的对象数组,这些对象指向了加载类时候查找的位置。任何以/结尾的URL都假设是一个目录。否则,URL会Otherwise, the URL假定是一个将被下载并在需要的时候打开的JAR文件。
    注意:在一个servlet容器里边,一个类加载器可以找到servlet的地方被称为资源库(repository)。
    在我们的应用程序里边,类加载器必须查找的地方只有一个,如工作目录下面的webroot目录。因此,我们首先创建一个单个URL组成的数组。URL类提供了一系列的构造方法,所以有很多中构造一个URL对象的方式。对于这个应用程序来说,我们使用Tomcat中的另一个类的相同的构造方法。这个构造方法如下所示。

public URL(URL context, java.lang.String spec, URLStreamHandler hander)
throws MalformedURLException

    你可以使用这个构造方法,并为第二个参数传递一个说明,为第一个和第三个参数都传递null。不过,这里有另外一个接受三个参数的构造方法:

public URL(java.lang.String protocol, java.lang.String host,
    java.lang.String file) throws MalformedURLException

    因此,假如你使用下面的代码时,编译器将不会知道你指的是那个构造方法:

new URL(null, aString, null);

    你可以通过告诉编译器第三个参数的类型来避开这个问题,例如。

URLStreamHandler streamHandler = null;
new URL(null, aString, streamHandler);

    你可以使用下面的代码在组成一个包含资源库(servlet类可以被找到的地方)的字符串,并作为第二个参数,

String repository = (new URL("file", null,
classPath.getCanonicalPath() + File.separator)).toString() ;

    把所有的片段组合在一起,这就是用来构造适当的URLClassLoader实例的process方法中的一部分:
// create a URLClassLoader
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
String repository = (new URL("file", null,
classPath.getCanonicalPath() + File.separator)).toString() ;
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);

    注意: 用来生成资源库的代码是从org.apache.catalina.startup.ClassLoaderFactory的createClassLoader方法来的,而生成URL的代码是从org.apache.catalina.loader.StandardClassLoader的addRepository方法来的。不过,在以下各章之前你不需要担心这些类。
    当有了一个类加载器,你可以使用loadClass方法加载一个servlet:

Class myClass = null;
try {
    myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
    System.out.println(e.toString());
}

    然后,process方法创建一个servlet类加载器的实例, 把它向下转换(downcast)为javax.servlet.Servlet, 并调用servlet的service方法:

Servlet servlet = null;
try {
    servlet = (Servlet) myClass.newInstance();
    servlet.service((ServletRequest) request,(ServletResponse) response);
}
catch (Exception e) {
    System.out.println(e.toString());
}
catch (Throwable e) {
    System.out.println(e.toString());
}

运行应用程序
    要在Windows上运行该应用程序,在工作目录下面敲入以下命令:

java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer1

    在Linux下,你使用一个冒号来分隔两个库:

java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer1

    要测试该应用程序,在浏览器的地址栏或者网址框中敲入:

http://localhost:8080/index.html

    或者

http://localhost:8080/servlet/PrimitiveServlet

    当调用PrimitiveServlet的时候,你将会在你的浏览器看到下面的文本:

Hello. Roses are red.

    请注意,因为只是第一个字符串被刷新到浏览器,所以你不能看到第二个字符串Violets are blue。我们将在第3章修复这个问题。

应用程序2
    第一个应用程序有一个严重的问题。在ServletProcessor1类的process方法,你向上转换ex02.pyrmont.Request实例为javax.servlet.ServletRequest,并作为第一个参数传递给servlet的service方法。你也向下转换ex02.pyrmont.Response实例为javax.servlet.ServletResponse,并作为第二个参数传递给servlet的service方法。

try {
    servlet = (Servlet) myClass.newInstance();
    servlet.service((ServletRequest) request,(ServletResponse) response);
}

    这会危害安全性。知道这个servlet容器的内部运作的Servlet程序员可以分别把ServletRequest和ServletResponse实例向下转换为ex02.pyrmont.Request和ex02.pyrmont.Response,并调用他们的公共方法。拥有一个Request实例,它们就可以调用parse方法。拥有一个Response实例,就可以调用sendStaticResource方法。
    你不可以把parse和sendStaticResource方法设置为私有的,因为它们将会被其他的类调用。不过,这两个方法是在个servlet内部是不可见的。其中一个解决办法就是让Request和Response类拥有默认访问修饰,所以它们不能在ex02.pyrmont包的外部使用。不过,这里有一个更优雅的解决办法:通过使用facade类。请看Figure 2.2中的UML图。
                            Figure 2.2: Façade classes


    在这第二个应用程序中,我们增加了两个façade类: RequestFacade和ResponseFacade。RequestFacade实现了ServletRequest接口并通过在构造方法中传递一个引用了ServletRequest对象的Request实例作为参数来实例化。ServletRequest接口中每个方法的实现都调用了Request对象的相应方法。然而ServletRequest对象本身是私有的,并不能在类的外部访问。我们构造了一个RequestFacade对象并把它传递给service方法,而不是向下转换Request对象为ServletRequest对象并传递给service方法。Servlet程序员仍然可以向下转换ServletRequest实例为RequestFacade,不过它们只可以访问ServletRequest
分享到:
评论

相关推荐

    How Tomcat Works 中文版+例程源码

    《How Tomcat Works》是一本深入探讨Apache Tomcat工作原理的书籍,中文版的提供使得国内开发者能够更方便地理解这一流行的开源Java Servlet容器。这本书不仅涵盖了Tomcat的基础知识,还详细解析了其内部机制,对于...

    How Tomcat Works 中文版.pdf

    《How Tomcat Works》中文版一书详细剖析了Tomcat服务器的内部工作机制。该书基于Tomcat 4.1.12和5.0.18两个版本,深入讲解了其servlet容器的架构和运作原理,尤其是代号为Catalina的核心组件。 Tomcat是一个开源的...

    How Tomcat Works 中文版

    《How Tomcat Works》是一本深入探讨Apache Tomcat工作原理的中文版书籍,对于Java Web开发者来说,理解Tomcat的工作机制至关重要。Tomcat是Apache软件基金会的Jakarta项目中的一个核心部分,它是一个开源的、免费的...

    How Tomcat Works 中文版/英文版 + 源码

    这本书的中文版和英文版都为读者提供了便利,无论你是母语为中文还是英文,都能无障碍地学习。同时,附带的源码更是让读者能够亲手实践,加深对Tomcat运行机制的理解。 Tomcat是Apache软件基金会的一个开源项目,它...

    How tomcat works 中文版

    《How Tomcat Works 中文版》是一份深入探讨Apache Tomcat服务器工作原理的指南,适合初学者和有经验的开发者阅读。Tomcat是Java Servlet和JavaServer Pages (JSP) 技术的主要开源实现,是许多Web应用部署的基础。这...

    HowTomcatWorks中文版

    HowTomcatWorks中文版HowTomcatWorks中文版 http://www.docin.com/p-58566711.html# suse安装oracle10 http://www.cnblogs.com/zhou__zhou/archive/2009/06/23/oracle_install.html

    tomcat工作原理深入详解——HowTomcatWorks中文版.pdf

    tomcat工作原理深入详解——HowTomcatWorks中文版.pdf

    how tomcat works中文版.pdf百度网盘下载地址

    根据提供的文件信息,本文将围绕“Tomcat工作原理”...最后,如果您希望通过文档进一步深入学习,请参考提供的下载链接获取《how tomcat works中文版》PDF文件。这将有助于您更系统地了解Tomcat的相关技术和最佳实践。

    HowTomcatWorks 中文版+源码.rar

    《HowTomcatWorks》是一本深入解析Apache Tomcat工作原理的书籍,中文版的发布使得更多的中国开发者能够理解和掌握这款广泛应用的开源Java Servlet容器的工作机制。Tomcat是Apache软件基金会Jakarta项目的一部分,它...

Global site tag (gtag.js) - Google Analytics