[How Tomcat Works]第2章 一个简单的Servlet容器
来源:互联网 发布:微商用什么软件 编辑:程序博客网 时间:2024/06/04 23:28
译者 jarfield
博客 http://jarfield.javaeye.com
- 概述
- javax.servlet.Servlet接口
- 第一个应用
- HttpServer1类
- Request类
- Response类
- StaticResourceProcessor类
- ServletProcessor1类
- 运行应用
- 第二个应用
- 运行应用
- 总结
概述
本章通过两个应用程序,介绍了如何开发你自己的Servlet容器。为了让你容易Servlet容器的工作原理,第一个应用程序被设计地尽可能简单,然后演化为稍微复杂一些的第二个Servlet容器。
提示:每章的Servlet容器都是在前一章的基础上逐步演化的,到第17章就变成了一个羽翼丰满(full-functional)的Tomcat Servlet容器。
本章的两个Servlet容器,既能够运行简单的Servlet,也能够处理静态资源。你可以使用PrimitiveServlet来测试本章的Servlet容器。PrimitiveServlet类的代码在Listing 2.1中,源代码文件在webroot目录下。本章的Servlet容器还不能运行更加复杂的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;
}
}
本章两个应用(Servlet容器)的类都在ex02.pyrmont包中。为了理解这两个应用是如何工作的,你需要熟悉javax.servlet.Servlet接口。因此,本章的第一节先讨论该接口。然后你将学习到,为了处理到Servlet的HTTP请求,Servlet容器必须做哪些事情。
javax.servlet.Servlet接口
Servlet编程主要涉及到javax.servlet和javax.servlet.http这两个包中的类和接口。在这些类和接口中,javax.servlet.Servlet接口是最重要的。所有的Servlet都必须实现该接口,或继承该接口的实现类。
Servlet接口有5个方法,它们的原型如下所示。
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()
在上面5个方法中,init、service和destroy是Serlvet的生命周期方法。Serlvet容器初始化Servlet之后会调用init方法。Servlet容器只调用该方法一次,以表明Servlet可以投入使用了。init方法执行成功前,Servlet不可以接收任何请求。Servlet程序员可以重写(override)该方法,以添加仅需执行一次的代码,例如加载数据库驱动、初始化一些变量的值等等。其它情况下,该方法一般留空。
接收到请求之后,Servlet容器以一个javax.servlet.ServletRequest对象和一个javax.servlet.ServletResponse对象为参数,调用Servlet的service方法。ServletRequest对象包含了客户端的请求信息,而ServletResponse对象封装了Servlet的响应。service方法在Servlet的生命周期中可以被调用多次。
在移除Servlet实例之前,Servlet容器会调用destroy方法。这通常发生在Servlet容器关闭或内存不够的时候。该方法只有在所有线程都退出service方法或超时之后,才能被调用。Serlvet容器调用了一个Servlet的destroy方法之后,就不会调用该Servlet的service方法。destroy方法给了Servlet一个机会,用来清理持有的资源,例如内存、文件句柄和线程,还可以用来持久化Servlet的状态。
Listing 2.1是PrimitiveServlet类的代码, 该Serlvet非常简单,我们用它来测试本章的Serlvet容器。PrimitiveServlet实现了javax.servlet.Servlet接口 (就像所有Servlet都必须做的那样),提供了所有Servlet接口所有5个方法的实现。每次调用init、service和destroy方法,PrimitiveServlet都把方法名打印到标准控制台。 另外,service方法从ServletResponse对象中获取了java.io.PrintWriter对象,用来向浏览器发送字符串。
第一个应用
现在,让我们从Servlet容器的视角来看看Servlet编程。简而言之,一个功能完全的Serlvet容器需要为每个HTTP请求做以下的事情:
- Servlet第一次被调用时,加载Serlvet类,并调用Serlvet的init方法(只调用一次)。
- 为每个请求,创建一个javax.servlet.ServletRequest对象和一个javax.servlet.ServletResponse对象。
- 以ServletRequest对象和ServletResponse为参数,调用Servlet的service方法。
- 当Servlet被关闭时,调用Servlet的destroy方法,卸载(unload)Servlet类。
本章的第一个Servlet容器不是功能完全。因此,它只能运行简单的Servlet,没有调用init和destroy方法。相反,它做了以下事情:
- 等待HTTP请求。
- 创建一个ServletRequest对象和一个ServletResponse对象。
- 如果请求的是静态资源,就以ServletRequest对象和ServletResposne对象为参数,调用StaticResourceProcessor对象的process方法。
- 如果请求的是Servlet,就加载Servlet类,并以ServletRequest对象和ServletResposne对象为参数,调用Servlet的service方法。
提示:在这个Servlet容器中,每次请求Servlet,Servlet类都会被加载。
第一个Servlet容器共包含6个类:
- HttpServer1
- Request
- Response
- StaticResourceProcessor
- ServletProcessor1
- Constants
Figure 2.1 描绘了第一个Servlet容器的UML图。
该应用的入口(静态main方法)在HttpServer1类中。main方法创建了HttpServer1的一个实例,并且调用了它的await方法。await方法在等待HTTP请求,并为每个请求创建一个Request对象和Response对象,然后将它们派发(dispatch)到StaticResourceProcessor对象或ServletProcessor对象,派发的依据是:请求的内容是静态资源还是Servlet。
Figure 2.1: The UML diagram of the first servlet container
Constants类包含了被其他类引用的静态常量WEB_ROOT。容器可以提供PrimitiveServlet和静态资源,WEB_ROOT就指定了PrimitiveServlet和静态资源所在的目录。
HttpServer1对象一直等待HTTP请求,直到接收到shutdown命令位置。你可以用第1章中的方法发送shutdowm命令。
本应用的每个类会在以下各小节中进行讨论。
HttpServer1类
本应用的HttpServer1类,和第1章简单Web服务器中的HttpServer类很类似。不过,HttpServer1类既可以处理静态资源,又可以处理Servlet。要请求一个静态资源,你可以在浏览器地址栏中敲入下面的URL:
http://machineName:port/staticResource
这就是你在第1章中请求静态资源的方法。
要请求Servlet,你可以使用下面的URL:
http://machineName:port/servlet/servletClass
因此,如果你使用本地浏览器请求名为PrimitiveServlet的Serlvet, 你可以在浏览器地址栏中敲入下面的URL:
http://localhost:8080/servlet/PrimitiveServlet
Servlet容器可以提供PrimitiveServlet。但是,如果你调用其他的Servlet,比如ModernServlet,Servlet容器就会抛出异常。下一章,你将会构建同时提供这两个Servlet的应用。
HttpServer1类的代码在Listing 2.2中。
Listing 2.2: The HttpServer1 Class's await method
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/开头,那么就会被派发到ServletProcessor对象。否则,请求将被派发到StaticResourceProcessor对象。注意Listing 2.2中灰色的部分。
Request类
Servlet的service方法从容器接收一个javax.servlet.ServletRequest对象和一个javax.servlet.ServletResponse对象作为参数。这也就是说,对每个HTTP请求,Servlet容器都必须创建一个ServletRequest对象和ServletResponse对象,并将它们传递给处理该请求的Servlet的service方法。
ex02.pyrmont.Request类代表了传递给Servlet的service方法的请求对象。因此,它必须实现javax.servlet.ServletRequest接口。Request类必须提供ServletRequest接口所有方法的实现。不过,我们想让Reuqest类尽量简单,只实现其中一些方法,在后面章节再实现所有的方法。为了通过编译,你必须提供这些方法的“空”实现。从Listing 2.3可以看出,所有返回值类型为Object的方法都返回null。
Listing 2.3: The Request class
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接口。因此,Response类必须提供ServletResponse接口所有方法的实现。和Request类一样,除了getWriter,我们将所有方法的实现“留空”。
Listing 2.4: The Response class
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构造函数的第二个参数是布尔类型,表明是否启用自动刷新(autoflush)功能。第二个参数如果为true,所有的println调用均会刷新到输出流中。不过,调用print方法是不会刷新的。
因此,如果Servlet的service方法的最后一行恰好调用了print方法,那么print的输出是不会被发送到浏览器的。本节的应用就有这个问题,我们将在下一章修复。
Response类仍包括第1章中讨论过的sendStaticResource方法。
StaticResourceProcessor类
ex02.pyrmont.StaticResourceProcessor类提供静态资源服务。该类只有一个process方法,代码见Listing 2.5。
Listing 2.5: The StaticResourceProcessor class
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: The ServletProcessor1 class
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对象中,该方法通过调用geUri方法获得请求URI:
String uri = request.getUri();
记住,URI的形式如下:
/servlet/servletName
其中servletName是Servlet类的名称。要加载该Serlvet类,我们需要从URI中知道Servlet的名称。我们可以用process方法的下一行来获取Servlet的名称:
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
接下来,process方法开始加载servlet。为此,你需要创建一个类加载器(class loader),并且告诉它,要加载的类所在的位置。对于这个Servlet容器,类加载器直接查找Constants.WEB_ROOT指向的位置,也就是工作目录下的webroot目录。
提示:我们将在第8章详细讨论类加载器。
要加载servet,你需要使用java.net.URLClassLoader类,它是java.lang.ClassLoader类的直接子类。 一旦有了URLClassLoader对象,你就可以调用它的loadClass来加载servlet类。实例化URLClassLoader类是直观易懂的。该类有3个构造函数,其中最简单的是:
public URLClassLoader(URL[] urls);
其中urls是java.net.URL对象的数组,这些URL指向了类搜索路径。我们假设,任何以/结束的URL都指向目录,其它URL都指向,需要时可以下载和打开的JAR文件。
提示:在servlet容器中,类加载器搜索servlet类文件的位置被称为仓库(repository)。
在我们的应用中,类加载器只需要查找一个路径,即工作目录下的webroot目录。因此,我们先创建了一个长度仅为1的URL数组。URL提供了很多构造函数,所以有多种方式创建URL对象。对于本应用,我们和Tomcat使用相同的构造函数。该构造函数的签名如下所示:
public URL(URL context, java.lang.String spec, URLStreamHandler hander) throws MalformedURLException
你可以这样使用上述构造函数,第一个参数传null,第二个参数传一个有效的spec,第三个值传null。但是,这里还有另一个接收三个参数的构造函数:
public URL(java.lang.String protocol, java.lang.String host, java.lang.String file) throws MalformedURLException
因此,如果你以如下方式创建URL对象,编译器将不知道调用哪个版本的构造函数。
new URL(null, aString, null);
要避免这个问题,你可以告诉编译器第三个参数的类型,就像这样:
URLStreamHandler streamHandler = null;
new URL(null, aString, streamHandler);
对于第二个参数,你传递了一个代表仓库(servlet类文件所在的目录)的String对象,代码如下:
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
把这些代码片段组合在一起,就是process方法中创建URLClassLoader对象的代码:
// 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,然后调用其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”, 这是因为只有第一个字符串被flush到浏览器。不过,我们将在第3章修复这个问题。
第二个应用
第一个应用有个严重的问题。在ServletProcessor1类的process方法中,你将ex02.pyrmont.Request对象向上转型(upcast)到javax.servlet.ServletRequest,并将它作为servlet的service方法的第一个参数。同样,你也将ex02.pyrmont.Response对象向上转型到javax.servlet.ServletResponse,并将它作为servlet的service方法的第二个参数。
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request, (ServletResponse) response);
}
这是对安全性的一种妥协(compromise)。了解Servlet容器内部细节的Servlet程序员,可以将ServletRequest对象和ServletResponse对象分别转回到ex02.pyrmont.Request和ex02.pyrmont.Response,并调用它们的public方法。有了Request对象,他们(指 Servlet程序员)就可以它的parse方法。有了Resposne对象,他们就可以调用它的sendStaticResource方法。
你不能将parse和sendStaticResource方法设置成private,否则它们就不能被其他类调用了。但是,这两个方法对servlet应该是不可见的。我们的解决方案是让Request和Response类都具有默认访问修饰符,这样它们就不能在ex02.pyrmont包外被使用。不过,这里有一个更优雅的方法:使用门面(facade)类。请看Figure 2.2中的UML图。
Figure 2.2: Façade classes
在第二个应用中,我们增加了两个facade类:RequestFacade和ResponseFacade。RequestFacade实现了ServletRequest接口,并通过接受一个Request对象的构造函数进行实例化,其中Request对象将赋值给一个类型为ServletRequest的成员变量。但是,该成员变量的访问类型是,因此不能在RequestFacade类的外部被访问。我们构造了一个RequestFacade对象传递给service方法,而不是将Request对象向上转型成ServletRequest对象再传递给service方法。Servlet程序员仍可以将ServletRequest对象向下转型回RequestFacade,但是他们只能访问SerlvetRequest接口中的方法。现在,parseUri方法就安全了。
Listing 2.7展现了RequestFacade类的部分代码。
Listing 2.7: The RequestFacade class
package ex02.pyrmont;
public class RequestFacade implements ServletRequest {
private ServletRequest request = null;
public RequestFacade(Request request) {
this.request = request;
}
/* implementation of the ServletRequest*/
public Object getAttribute(String attribute) {
return request.getAttribute(attribute);
}
public Enumeration getAttributeNames() {
return request.getAttributeNames();
}
...
}
请注意Request的构造函数。它接受了一个Request对象但是立即将其赋值给private的ServletRequest对象。同样也请注意RequestFacade类的每个方法都调用了ServletRequest对象的相应方法。
ResponseFacade也是这么做的。
第二个应用使用了下面的类:
- HttpServer2
- Request
- Response
- StaticResourceProcessor
- ServletProcessor2
- Constants
HttpServer2类和HttpServer1基本相同,只是它的await方法使用了ServletProcessor2类,而不是ServletProcessor1:
if (request.getUri().startWith("/servlet/")) {
servletProcessor2 processor = new ServletProcessor2();
processor.process(request, response);
}
else {
...
}
ServletProcessor2类和ServletProcessor1基本相同,除了下面列出的process方法的部分代码代码:
Servlet servlet = null;
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);
}
运行应用
要在Windows运行该应用,在工作目录运行以下命令:
java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer2
在Linux上,你可以使用冒号分隔两个库:
java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer2
你可以使用与第一个应用相同的URLs,并且将会看到相同的结果。
总结
本章讨论了两个简单的Servlet容器,它们即可以处理静态资源,也可以处理PrimitiveServlet这样简单的servlet。同时,本章也给出了javax.servlet.Servlet接口和相关类型的背景信息。
- [How Tomcat Works]第2章 一个简单的Servlet容器
- 《How To Tomcat Works》-第2章:一个简单的Servlet容器
- how tomcat works 读书笔记(二)----------一个简单的servlet容器
- How Tomcat Works(Scala语言) 02 一个简单的Servlet容器
- How tomcat works——2 一简单的Servlet容器
- [How Tomcat Works]第1章 一个简单的Web服务器
- how tomcat works 五 servlet容器 上
- how tomcat works 5 servlet容器 下
- tomcat(2)一个简单的servlet容器
- 《How To Tomcat Works》-第一章:一个简单的Web服务器
- how tomcat works 读书笔记(一)----------一个简单的web服务器
- 《how tomcat works》第一章 构建一个简单的web服务器
- How Tomcat works之(一个简单的web服务器)
- 《How To Tomcat Works》-第五章 容器
- [How Tomcat Works]第0章 介绍
- [How Tomcat Works]第3章 连接器
- [深入剖析Tomcat]一个简单的servlet容器实现2
- How Tomcat Works 2
- 抓取内核信息(一)-从简单入手
- J2ME在移动设备上实现动画
- 创业投资——证券分析
- js-window对象的方法和属性资料
- WinInet 介绍
- [How Tomcat Works]第2章 一个简单的Servlet容器
- C++的iostream标准库介绍
- 利用J2ME与ASP建立数据库连接
- Java类的一些认识
- 去除javascript数组中的相同元素
- j2me学习 J2me Wap Explorer
- 怎样使用ActiveX DLL 来封装窗体Form为DLL文件
- 关于.dll.a文件的几个小知识点
- 您了解Excel的全新面孔嘛?