精华帖 (3) :: 良好帖 (14) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2010-04-10
最后修改:2010-04-10
译者 jarfield博客 http://jarfield.iteye.com概 述 就像《简介》中介绍的,Catalina
中有两个主要模
块:Connector
(连接器)和Container
(容器)。本章,你将编写一个连接
器
来增强第2章的应用,该连接器
能够创建更好的Request
和Response
对
象。符合Servlet 2.3
和2.4
规范的连接器
必须创建javax.servlet.http.HttpServletRequest
实例和javax.servlet.http.HttpServletResponse
实例,并将它们作
为参数传递给servlet
的service
方法。第2章的Servlet
容器只
能运行实现了javax.servlet.Servlet
接口的servlet
,并传递javax.servlet.ServletRequest
实
例和javax.servlet.ServletResponse
实例给servlet
的service
方
法。连接器
并不知道servlet
的类型(例如,是否实现了javax.servlet.Servlet
接
口, 继承了javax.servlet.GenericServlet
,或继承了javax.servlet.http.HttpServlet
), 因此它必须始终提供HttpServletRequest
实例和HttpServletResponse
实
例。 StringManager 类 像Tomcat
这样的大型程序,都需要仔细地处理错误消息。在Tomcat
中,
错误消息对系统管理员和Servlet
程序员都很重要。例如,通过Tomcat
的错误日志,系统管理员可以轻松定位任何异常。Tomcat
为内部抛出的每个javax.servlet.ServletException
打
印出一条特定错误日志,这样Servlet
程序员就可以知道自己写的servlet
哪里出了问题。 private static Hashtable managers = new Hashtable(); public synchronized static StringManager getManager(String packageName) { StringManager mgr = (StringManager)managers.get(packageName); if (mgr == null) { mgr = new StringManager(packageName); managers.put(packageName, mgr); } return mgr; }
提示:在附带的zip
文件中,可以找到一篇题为“The Singleton Pattern
”、关于单例模式的文章。
StringManager sm = StringManager.getManager("ex03.pyrmont.connector.http");
在 ex03.pyrmont.connector.http 包 中,你可以找到三个属性文件:LocalStrings.properties 、LocalStrings_es.properties 和LocalStrings_ja.properties 。StringManager 实例根据应用程序运行时所在机器的区域(local ) 来决定使用哪个文件。如果你打开LocalStrings.properties ,非 注释的第一行应该是这样的: httpConnector.alreadyInitialized=HTTP connector has already been initialized
public String getString(String key)
以“httpConnector.alreadyInitialized ” 为参数调用getString 方法,就会返回“HTTP connector has already been initialized ”。 应用程序 从本章开始,每章附带的应用程序被化分成模块。本章的应用包括三个模块:connector
、startup
和core
。
启动应用 我们从ex03.pyrmont.startup.Bootstrap
类启动整个应用。Listing 3.1
列出了该类的代码。 package ex03.pyrmont.startup; import ex03.pyrmont.connector.http.HttpConnector; public final class Bootstrap { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); connector.start(); } }
Bootstrap
类的main
方
法创建了一个HttpConnector
实例,并调用了它的start
方法。Listing
3.2
列出了HttpConnector
类的代码。 package ex03.pyrmont.connector.http; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; public class HttpConnector implements Runnable { boolean stopped; private String scheme = "http"; public String getScheme() { return scheme; } public void run() { 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); } while (!stopped) { // Accept the next incoming connection from the server socket Socket socket = null; try { socket = serverSocket.accept(); } catch (Exception e) { continue; } // Hand this socket off to an HttpProcessor HttpProcessor processor = new HttpProcessor(this); processor.process(socket); } } public void start() { Thread thread = new Thread(this); thread.start (); } } 连接器 ex03.pyrmont.connector.http.HttpConnector
类
代表了连接器
,其职责是创建等待HTTP
请求的服务器套接字。Listing 3.2
给
出了该类的代码。
提示:
run
方法和第
2
章中
HttpServer1
类的
await
方法是相同的。
马上你就能看到,HttpConnector 类和ex02.pyrmont.HttpServer1 类非常相似。从java.net.ServerSocket 类的accept 方法获得一个socket 之后发生了变 化,HttpConnector 类创建了一个HttpProcessor 实例,并以socket 为 参数调用其process 方法。 提示: HttpConnector 类拥有另一个名为 getSchema 的方法,该方法返回网络请求的 schema ( HTTP )。 HttpProcessor 类 的process 方法接受HTTP 请 求的socket 为参数。对于每个HTTP 请求,process 方法会做如下处 理:
就像第2 章里那样,ServletProcessor 调 用了被请求的servlet 的service 方法,StaticResourceProcessor 发 送静态资源的内容(给客户端)。 Listing 3.3 列 出了process 方法的代码。 Listing 3.3: The HttpProcessor class's process method. public void process(Socket socket) { SocketInputStream input = null; OutputStream output = null; try { input = new SocketInputStream(socket.getInputStream(), 2048); output = socket.getOutputStream(); // create HttpRequest object and parse request = new HttpRequest(input); // create HttpResponse object response = new HttpResponse(output); response.setRequest(request); response.setHeader("Server", "Pyrmont Servlet Container"); parseRequest(input, output); parseHeaders(input); //check if this is a request for a servlet or a static resource //a request for a servlet begins with "/servlet/" if (request.getRequestURI().startsWith("/servlet/")) { ServletProcessor processor = new ServletProcessor(); processor.process(request, response); } else { StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } // Close the socket socket.close(); // no shutdown for this application } catch (Exception e) { e.printStackTrace (); } } process
方法首先从获取socket
的输入流和输出流。注意,该方法使用的SocketInputStream
继
承自java.io.InputStream
。
SocketInputStream input = null; OutputStream output = null; try { input = new SocketInputStream(socket.getInputStream(), 2048); output = socket.getOutputStream(); // Then, it creates an HttpRequest instance and an HttpResponse instance and assigns // the HttpRequest to the HttpResponse. // create HttpRequest object and parse request = new HttpRequest(input); // create HttpResponse object response = new HttpResponse(output); response.setRequest(request); 本章应用的HttpResponse 类比第2 章 的Response 类要复杂很多。举例来说,你可以通过调用HttpResponse 类的setHeader 方 法向客户端发送headers 。 response.setHeader("Server", "Pyrmont Servlet Container"); 接下来,process
方
法调用HttpProcessor
类的两个私有方法来解析请求。
parseRequest(input, output); parseHeaders (input); 然后,process方法根据请求URI的模式(pattern),将HttpRequest对象和HttpResponse对像甩给(hand off ... to)一个 ServletProcessor对象和一个 StaticResourceProcessor对象。 if (request.getRequestURI().startsWith("/servlet/")) { ServletProcessor processor = new ServletProcessor(); processor.process(request, response); } else { StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } 最后,process 方法关闭socket 。 socket.close(); 同样注意,HttpProcessor
类
使用org.apache.catalina.util.StringManager
类
来发送错误消息:
protected StringManager sm = StringManager.getManager("ex03.pyrmont.connector.http"); HttpProcessor 类的私有方法——parseRequest 、parseHeaders 和normalize —— 被调用来帮助填充HttpRequest 对象。在下一节“创建HttpRequest 对象”,我们将讨论这些方法。 创建HttpRequest 对象HttpRequest 类 实现了javax.servlet.http.HttpServletRequest 接 口。附带还有一个叫做HttpRequestFacade 的门面类。Figure 3.2 展现了HttpRequest 和 相关类的类图。HttpRequest 类 的许多方法都是留空的(等待第4章才会全部实现),但是servlet 程序员 已经可以从HTTP 请求中获得headers 、cookies 和请求参数。这 三种值被存储在下面的引用变量中: protected HashMap headers = new HashMap(); protected ArrayList cookies = new ArrayList(); protected ParameterMap parameters = null; 提示:我们会在“获取参数”小节解释 ParameterMap 类。 因此,servlet 程序员可以从javax.servlet.http.HttpServletRequest 下面这些方法中获取正确的值:getCookies 、getDateHeader 、getHeader 、getHeaderNames 、getHeaders 、getParameter 、getPrameterMap 、getParameterNames 和getParameterValues 。正如你在HttpRequest类中看到的,一旦获得headers 、cookies 和 请求参数,相关方法的实现就很简单了。 不用说,这里主要的挑战就是解析HTTP请求和填充HttpRequest 对象。对于headers 和cookies ,HttpRequest 类 提供了addHeader 和addCookie 方 法,HttpProcessor 类的prseHeaders 就调用了这两个方法。 请求参数是在需要时才被HttpRequest 类 的parseParameters 方法解析的。本节所有的方法都会被讨论到。 由于解析HTTP 请求是一个非常复杂的任务,因此本节被分成下面几个小节:
读取套接字的输入流 在第1
、2
章中,ex01.pyrmont.HttpRequest
类和ex02.pyrmont.HttpRequest
类已经做了一部分解析HTTP
请求的工作。通过调用java.io.InputStream
类
的read
方法,我们可以从请求行获得HTTP
方法、URI
和HTTP
版本: byte[] buffer = new byte [2048]; try { // input is the InputStream from the socket. i = input.read(buffer); }
SocketInputStream input = null; OutputStream output = null; try { input = new SocketInputStream(socket.getInputStream(), 2048); ...
正如前面提到的,使用SocketInputStream 类的原因是为了使用它的两 个重要方法:readRequestLine 和readHeader 。继续往下读。 解析请求行HttpProcessor 类的process 方 法调用私有方法parseRequest 来解析请求行,即HTTP 请求的第一行。这里给出请求行的一个例子: GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1
/myApp/ModernServlet
userName=tarzan&password=pwd
private void parseRequest(SocketInputStream input, OutputStream output) throws IOException, ServletException { // Parse the incoming request line input.readRequestLine(requestLine); String method = new String(requestLine.method, 0, requestLine.methodEnd); String uri = null; String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd); // Validate the incoming request line if (method, length () < 1) { throw new ServletException("Missing HTTP request method"); } else if (requestLine.uriEnd < 1) { throw new ServletException("Missing HTTP request URI"); } // Parse any query parameters out of the request URI int question = requestLine.indexOf("?"); if (question >= 0) { request.setQueryString(new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1)); uri = new String(requestLine.uri, 0, question); } else { request.setQueryString(null); uri = new String(requestLine.uri, 0, requestLine.uriEnd); } // Checking for an absolute URI (with the HTTP protocol) if (!uri.startsWith("/")) { int pos = uri.indexOf("://"); // Parsing out protocol and host name if (pos != -1) { pos = uri.indexOf('/', pos + 3); if (pos == -1) { uri = ""; } else { uri = uri.substring(pos); } } } // Parse any requested session ID out of the request URI String match = ";jsessionid="; int semicolon = uri.indexOf(match); if (semicolon >= 0) { String rest = uri.substring(semicolon + match,length()); int semicolon2 = rest.indexOf(';'); if (semicolon2 >= 0) { request.setRequestedSessionId(rest.substring(0, semicolon2)); rest = rest.substring(semicolon2); } else { request.setRequestedSessionId(rest); rest = ""; } request.setRequestedSessionURL (true); uri = uri.substring(0, semicolon) + rest; } else { request.setRequestedSessionId(null); request.setRequestedSessionURL(false); } ((HttpRequest) request).setMethod(method); request.setProtocol(protocol); if (normalizedUri != null) { ((HttpRequest) request).setRequestURI(normalizedUri); } else { ((HttpRequest) request).setRequestURI(uri); }
if (normalizedUri == null) { throw new ServletException("Invalid URI: " + uri + "'"); } 解 析HeadersHttpHeader 类描述了HTTP 头部。第4 章 将详细解释该类,现在我们只要知道下面几点就足够了:
String name = new String(header.name, 0, header.nameEnd); String value = new String(header.value, 0, header.valueEnd); parseHeaders
方法包括了一个while
循
环,持续从SocketInputStream
中读取headers
,直到没有header
为
止。该循环首先创建一个HttpHeader
对象,并将它传递给SocketInputStream
类的readHeader
方
法:
HttpHeader header = new HttpHeader(); // Read the next header input.readHeader(header); 然后,通过测试 HttpHeader 实例的nameEnd 和valueEnd 域来判断 输入流中有没有更多的header 可以读取: if (header.nameEnd == 0) { if (header.valueEnd == 0) { return; } else { throw new ServletException (sm.getString("httpProcessor.parseHeaders.colon")); } } 如果有下一个header ,就可以获取header 的名称和值: String name = new String(header.name, 0, header.nameEnd); String value = new String(header.value, 0, header.valueEnd); 一旦获得了header 的名称和值,就调用HttpRequest 对 象的addHeader 方法,将它们添加到用于存储header 的HashMap 中: request.addHeader(name, value); 有的header 还需要设置一些属性。例如,当servlet 调 用javax.servlet.ServletRequest 的getContentLength 方法时,就返回content-length 的值。包含cookie 的cookie header 需要被添加到cookie 集合(collection )中。 于是,我们还需要做下面的处理: if (name.equals("cookie")) { ... // process cookies here } else if (name.equals("content-length")) { int n = -1; try { n = Integer.parseInt (value); } catch (Exception e) { throw new ServletException(sm.getString( "httpProcessor.parseHeaders.contentLength")); } request.setContentLength(n); } else if (name.equals("content-type")) { request.setContentType(value); } Cookie 的解析在下一节“解析Cookies ”中讨论。 解析CookiesCookies 是作为HTTP 请求header 被 浏览器发送的。这种header 的名称是"cookie ",值是cookie 的 名/值对。这里有个例子,包含username 和password 两个cookie 的header :Cookie: userName=budi; password=pwd; 对Cookie 的解析,是通过org.apache.catalina.util.RequestUtil 类 的parseCookieHeader 方法完成的。该方法接受cookie header ,返回一个javax.servlet.http.Cookie 数 组。该数组元素的个数就是cookie header 中cookie 名/值对的数量。Listing 3.5 给出了parseCookieHeader 方法的代码。 Listing 3.5: The org.apache.catalina.util.RequestUtil class's parseCookieHeader method public static Cookie[] parseCookieHeader(String header) { if ((header == null) || (header.length() < 1) ) return (new Cookie[0]); ArrayList cookies = new ArrayList(); while (header.length() > 0) { int semicolon = header.indexOf(';'); if (semicolon < 0) semicolon = header.length(); if (semicolon == 0) break; String token = header.substring(0, semicolon); if (semicolon < header.length()) header = header.substring(semicolon + 1); else header = ""; try { int equals = token.indexOf('='); if (equals > 0) { String name = token.substring(0, equals).trim(); String value = token.substring(equals+1).trim(); cookies.add(new Cookie(name, value)); } } catch (Throwable e) { ; } } return ((Cookie[]) cookies.toArray (new Cookie [cookies.size ()])); } 这里是HttpProcessor 类parseHeader 方法中,负责处理cookies 的 那部分代码: else if (header.equals(DefaultHeaders.COOKIE_NAME)) { Cookie cookies[] = RequestUtil.ParseCookieHeader (value); for (int i = 0; i < cookies.length; i++) { if (cookies[i].getName().equals("jsessionid")) { // Override anything requested in the URL if (!request.isRequestedSessionIdFromCookie()) { // Accept only the first session id cookie request.setRequestedSessionId(cookies[i].getValue()); request.setRequestedSessionCookie(true); request.setRequestedSessionURL(false); } } request.addCookie(cookies[i]); } } 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-04-12
非常好,楼主辛苦了
|
|
返回顶楼 | |
发表时间:2010-04-12
看英文好辛苦啊,期待楼主的续作。。。
|
|
返回顶楼 | |
发表时间:2010-04-12
我好像有个 doc 的
|
|
返回顶楼 | |
发表时间:2010-04-13
lz写的比我详细多了,可以看成是翻译。
|
|
返回顶楼 | |
发表时间:2010-04-14
我一下子没有看懂,这里真是很深呀
|
|
返回顶楼 | |
浏览 6176 次