- 浏览: 202484 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
qigeminghaofan:
第二个应用中说第一个不安全,熟悉servlet内部的程序员能向 ...
[How Tomcat Works]第2章 一个简单的Servlet容器 -
lliiqiang:
substring内存泄露问题只需要再加一个方法,让程序员明白 ...
优化变成了忧患:String.split引发的“内存泄露” -
tonghaoqi__:
必须点赞。。
如何控制War包访问Tomcat的内部实现类 -
jzzwy:
好文章 支持
Tomcat 5.5.26源代码分析——启动过程(一) -
wangzhewang:
牛!,希望更多分享!
Linux随机数发生器导致Apache进程全部Block的问题追查
译者 jarfield
博客 http://jarfield.iteye.com
概述
本章通过两个应用程序,介绍了如何开发你自己的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; } }
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()
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
容器会调用dest
r
oy
方法。这通常发
生在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 中灰色的部分(59行~70行)。
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) { } }
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(); } }
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();
String uri = request.getUri();
记住,URI 的形式如下:
/servlet/servletName
其中servletName
是
Servlet
类的名称。要加载该
Serlvet
类,我们需要从
URI
中
知道
Servlet
的名称。我们可以用
process
方法的下一行来获取
Servlet
的名称:
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
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);
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
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
public URL(java.lang.String protocol, java.lang.String host, java.lang.String file) throws MalformedURLException
因此,如果你以如下方式创建URL 对象,编译器将不知道调用哪个版本的构造函数。
new URL(null, aString, null);
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();
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()); }
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(); } ... }
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 {
...
}
if (request.getUri().startWith("/servlet/")) { servletProcessor2 processor = new ServletProcessor2(); processor.process(request, response); } else { ... }
ServletProcessor2
类和ServletProcessor1
基本相同,除了下面列出的process
方法的部分代码
评论
The ServletProcessor2 class is similar to ServletProcessor1, except in the following part of its process method: Servlet servlet = null; RequestFacade requestFacade = new RequestFacade(request); ResponseFacade responseFacade = new ResponseFacade(response); try { servlet = (Servlet) myClass.newInstance(); servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade); }
FileInputstream应该是FileInputStream
request.append((char) buffer(j));
应该是:
request.append((char) buffer[j]);
发表评论
-
如何控制War包访问Tomcat的内部实现类
2010-12-14 21:56 6315Tomcat默认部署了Manager应 ... -
[How Tomcat Works]第4章 Tomcat默认连接器
2010-06-29 20:17 8547译者 jarfield 博 客 ht ... -
[How Tomcat Works]第3章 连接器(二)
2010-04-10 22:50 7829译者 jarfield 博客 http://j ... -
[How Tomcat Works]第3章 连接器(一)
2010-04-10 21:55 8718译者 jarfield 博客 http://j ... -
Tomcat 5.5.26源代码分析——启动过程(二)
2010-03-25 14:38 7778init方法 Catalina_Home和Cat ... -
Tomcat 5.5.26源代码分析——启动过程(一)
2010-03-25 14:30 5031main方法 第一阶段 第二阶段 ... -
[How Tomcat Works]第1章 一个简单的Web服务器
2010-03-02 23:09 5512译者 jarfield 博客 http:// ... -
Tomcat 5.5.26的网络构建指南
2010-02-27 00:44 33471. 安装JDK ... -
[How Tomcat Works]第0章 介绍
2010-02-24 19:09 3222译者 jarfield 博客 ... -
Eclipse远程调试Tomcat
2010-01-24 20:24 4824最近,一直在研究Tomcat的工作内幕,主要的方法就是参考《H ...
相关推荐
《译How Tomcat Works(第二章)》这篇文章主要讲解了Apache Tomcat服务器的工作原理,它是一个开源的Java Servlet容器,广泛用于部署Web应用程序。在这一章中,我们将深入探讨Tomcat如何处理HTTP请求,以及其内部架构...
《How Tomcat Works》是一本深入解析Apache Tomcat工作原理的书籍,同时也包含了源码,为读者提供了理论与实践相结合的深入学习体验。Tomcat是一款广泛使用的开源Java Servlet容器,它是Apache软件基金会 Jakarta...
本篇文章将深入探讨Tomcat,一个广泛使用的Java Web服务器,通过翻译分析"How Tomcat Works"一书的前四章内容,来理解其工作机制。 首先,我们要了解Tomcat的基本架构。Tomcat是Apache软件基金会的Jakarta项目中的...
Apache Tomcat是一个开源的Java Servlet容器,它实现了Java Servlet和JavaServer Pages技术规范,提供了Java Web服务器的功能。对于Java Web开发者,尤其是那些希望深入了解Web容器工作原理的学习者而言,这本书是一...
《译How Tomcat Works(第四章)》这篇文章深入解析了Apache Tomcat服务器的工作原理,主要聚焦于Tomcat的内部机制,对于理解Web应用容器的运行方式具有重要意义。Tomcat是Java Servlet和JavaServer Pages(JSP)...
本篇文章将深入剖析"HowTomcatWorks",帮助读者全面理解Tomcat的内部工作机制。 1. **Tomcat架构概述** Tomcat由一系列组件构成,包括Catalina(核心Servlet容器)、Jasper(JSP引擎)、 Coyote(HTTP/HTTPS连接器...
通过对《How Tomcat Works》一书第11章的翻译与分析,我们将揭示StandardWrapper如何工作,以及它对整个Web应用的影响。 首先,我们需要了解Servlet的生命周期。Servlet在Web应用中扮演着动态处理请求的角色,它们...
通过阅读和分析《How Tomcat Works》的第12章,我们可以更深入地了解Tomcat如何处理请求,管理Web应用,并为开发者提供一个强大的平台来构建和运行Java Web应用程序。这有助于我们更好地利用Tomcat的特性,提高应用...
《WEB服务器工作机制由浅至深(8):【How Tomcat Works】第14章Server和Service》 在深入探讨Web服务器的工作机制时,Tomcat作为Apache软件基金会的开源Java Servlet容器,扮演着至关重要的角色。本章将聚焦于Tomcat...
1. **Tomcat简介**:Tomcat是Apache软件基金会的Jakarta项目下的一个开源应用服务器,主要负责运行Java Servlet和JavaServer Pages (JSP)。作为轻量级的Web服务器和应用服务器,Tomcat因其简单、高效而被广泛使用。 ...
本文将深入探讨Tomcat的工作机制,基于"How Tomcat Works"的第五章和第六章进行翻译和分析。 第五章主要讨论了Tomcat的生命周期管理和容器概念。在Tomcat中,每个应用都有一个对应的Context,它管理Servlet的加载、...
第2章 一个简单的servlet容器 7 2.1 简述 7 2.2 javax.servlet.Servlet接口 7 2.3 Application 1 7 2.3.1 HttpServer1类 8 2.3.2 Request类 8 2.3.3 Response类 9 2.3.4 StaticResourceProcessor类 9 2.3.5 ...
【WEB服务器工作机制由浅至深(9):【How Tomcat Works】第16章 关闭钩子以及之后的章节简述】 在深入探讨Tomcat服务器的工作机制时,我们来到了第16章,这一章主要讨论了“关闭钩子”(Shutdown Hooks)的概念及其在...
Tomcat的源码是开放的,这对于开发者来说是一个宝贵的资源,可以学习到如何设计和实现一个高性能的WEB服务器。同时,工具如JMX(Java Management Extensions)可以帮助监控和管理Tomcat的运行状态,包括Engine、Host...