Tomcat剖析(三):连接器

Tomcat剖析(三)

目录:

  1. Tomcat剖析(一):一个简单的Web服务器
  2. Tomcat剖析(二):一个简单的Servlet服务器
  3. Tomcat剖析(三):连接器

大家都知道Catalina 中有两个主要的模块:连接器和容器。本节将HttpServer2完善为HttpConnect,创建一个更好的请求和响应对象的连接器,不仅仅是简简单单的调用自己的await方法,而是用线程启动

上一节是使用Reques对象,因为我们不知道请求的类型,而这一节中,如果是HTTP请求,那么Request对象中需要更多的信息,所以本节的重点是连接器(中的处理器)解析 HTTP 请求头部和cookie并让 servlet 可以获得头部, cookies, 参数名/值等等,不再只是解析URI。同时,将会完善第 2 节中 Response 类的 getWriter 方法,让它能够正确运行。由于这些改进,你将会从上一节的PrimitiveServlet 中获取一个完整的响应,并能够运行更加复杂的ModernServlet。

推荐先到我的github(地址在本文最后)上下载本书相关的代码,同时去上网下载这本书。 

上一节的HttpServer 类被分离为两个类:HttpConnector和 HttpProcessor,Request 被 HttpRequest 所取代,而 Response 被 HttpResponse 所取代。HttpServer 类的职责是等待 HTTP 请求并创建请求和响应对象。在本节的应用中,等待 HTTP 请求的工作交给 HttpConnector 实例,而创建请求和响应对象的工作交给了HttpProcessor 实例。

一个 HttpRequest 对象将会给转换为一个 HttpServletRequest 实例并传递给被调用的 servlet 的 service 方法。因此,每个 HttpRequest 实例必须适当增加字段,以便 servlet可以使用它们。值需要赋给 HttpRequest 对象,包括 URI,查询字符串,参数,cookies 和其他的头部等等(可以在代码中看到)。因为连接器并不知道被调用的 servlet(就是我们自己定义的servlet可能是很复杂的,需要请求中所有的参数) 需要哪个值,所以连接器必须从 HTTP 请求中解析所有可获得的值。

这一节的核心类有4个

(上一节讲过的类这一节有的重复出现的,不列出来了):

HttpConnector.java: 等待 HTTP 请求并创建请求和响应对象

HttpProcessor.java:创建请求和响应对象

SocketInputStream.java:负责直接从输入流中获取头部和请求行信息

RequestUtil.java:这节的功能是对请求头中cookie的解析

HttpHeader.java、HttpRequest.java、HttpRequestLine.java、HttpResponse.java分别封装了请求头,请求,请求行,响应需要的内容。HttpRequestFacade.java和HttpResponseFacade.java同样是为了安全性而写的,实现方式和上一节类似。ServletProcessor.java也只是将相应的类(如RequestFacade改为HttpRequestFacade)进行修改。

为了避免篇幅太长,解析头部和解析请求行以及如何获取请求将在下一次补充上。

HttpConnector.java:

1、等待 HTTP 请求

2、为每个请求创建个 HttpProcessor 实例

3、调用 HttpProcessor 的 process 方法

这个类是这一节中最简单的,不用详细说明了,不同的可以参考上一节

代码注释解释如下:

 1 package ex03.pyrmont.connector.http;
 2
 3 import java.io.IOException;
 4 import java.net.InetAddress;
 5 import java.net.ServerSocket;
 6 import java.net.Socket;
 7
 8 public class HttpConnector implements Runnable {
 9
10     boolean stopped;
11     private String scheme = "http";
12
13     // 返回一个 scheme,这里是HttpConnector,当然是http
14     public String getScheme() {
15         return scheme;
16     }
17
18     public void run() {
19         ServerSocket serverSocket = null;
20         int port = 8080;
21         try {
22             serverSocket = new ServerSocket(port, 1,
23                     InetAddress.getByName("127.0.0.1"));
24         } catch (IOException e) {
25             e.printStackTrace();
26             System.exit(1);
27         }
28         while (!stopped) {
29             // Accept the next incoming connection from the server socket
30             Socket socket = null;
31             try {
32                 socket = serverSocket.accept();
33             } catch (Exception e) {
34                 continue;
35             }
36             // 除了用线程启动外,这里也是与上节的不同之处,不从连接器中直接判断请求类型,而是交给HttpProcessor处理
37             HttpProcessor processor = new HttpProcessor(this);
38             processor.process(socket);
39         }
40     }
41
42     public void start() {
43         Thread thread = new Thread(this);
44         thread.start();
45     }
46 }

HttpProcessor.java

HttpProcessor 类的 process 方法接受前来的 HTTP 请求的套接字,会做下面的事情:

1. 创建一个 HttpRequest 对象。

2. 创建一个 HttpResponse 对象。

3. 解析 HTTP 请求的第一行和头部,并放到 HttpRequest 对象。

4.  解析 HttpRequest 和 HttpResponse 对 象 到 一 个 ServletProcessor 或者StaticResourceProcessor

所以这个类需要有HttpRequest、HttpRequestLine和HttpResponse实例

  1 package ex03.pyrmont.connector.http;
  2
  3 import ex03.pyrmont.ServletProcessor;
  4 import ex03.pyrmont.StaticResourceProcessor;
  5
  6 import java.net.Socket;
  7 import java.io.OutputStream;
  8 import java.io.IOException;
  9
 10 import javax.servlet.ServletException;
 11 import javax.servlet.http.Cookie;
 12
 13 import org.apache.catalina.util.RequestUtil;
 14 import org.apache.catalina.util.StringManager;
 15
 16 public class HttpProcessor {
 17
 18     public HttpProcessor(HttpConnector connector) {
 19         this.connector = connector;
 20     }
 21     //HttpProcessor对应的HttpConnector,由构造器传入
 22     private HttpConnector connector = null;
 23     private HttpRequest request;
 24     private HttpRequestLine requestLine = new HttpRequestLine();
 25     private HttpResponse response;
 26
 27     protected String method = null;
 28     protected String queryString = null;
 29
 30     //Tomcat的错误管理方式,将在本节最后讲解
 31     protected StringManager sm = StringManager
 32             .getManager("ex03.pyrmont.connector.http");
 33
 34     //由HttpConnector类调用,socket为当前发出请求的用户
 35     public void process(Socket socket) {
 36
 37         //SocketInputStream将在本文后面讲解
 38         SocketInputStream input = null;
 39         OutputStream output = null;
 40         try {
 41             //对获取输入流进行封装。
 42             //里面的readHeader(HttpHeader)和readRequestLine(HttpRequestLine)
 43             //用于直接从得到的流中获取Heander和RequestLine对象
 44             input = new SocketInputStream(socket.getInputStream(), 2048);
 45             output = socket.getOutputStream();
 46
 47             //创建HttpRequest对象
 48             request = new HttpRequest(input);
 49
 50             //创建HttpRequest对象
 51             response = new HttpResponse(output);
 52             response.setRequest(request);
 53             //这里可以设置响应给浏览器的响应头
 54             response.setHeader("Server", "Pyrmont Servlet Container");
 55
 56             //本节的重点,解析请求行和请求头
 57             parseRequest(input, output);
 58             parseHeaders(input);
 59
 60             //if else块和上一节一样,通过判断URI的不同调用不同的Processor处理器
 61             if (request.getRequestURI().startsWith("/servlet/")) {
 62                 ServletProcessor processor = new ServletProcessor();
 63                 processor.process(request, response);
 64             } else {
 65                 StaticResourceProcessor processor = new StaticResourceProcessor();
 66                 processor.process(request, response);
 67             }
 68
 69             //关闭socket
 70             socket.close();
 71             //注意:这个应用还没有关闭程序功能,需要自己强制关闭
 72         } catch (Exception e) {
 73             e.printStackTrace();
 74         }
 75     }
 76
 77     //功能:解析请求头
 78     //这个方法是org.apache.catalina.connector.http.HttpProcessor的简化版本
 79     //这个方法解析了一些“简单”的头部,像"cookie", "content-length","content-type",忽略了其他头部
 80     private void parseHeaders(SocketInputStream input) throws IOException,
 81             ServletException {
 82         //进入死循环,直到解析请求头结束跳出
 83         while (true) {
 84             HttpHeader header = new HttpHeader();
 85             //SocketInputStream流中获取Header对象,本文后面具体讲解
 86             //从SocketInputStream代码的readHeader中可以看到通过这里的while方法,一次提取出一个键值对的请求头的值
 87             //所以下面的name和value就获得到了相应的值
 88             //parseRequest也是使用这样的方法,下面就忽略了
 89             input.readHeader(header);
 90
 91             //检测 HttpHeader 实例的 nameEnd 和 valueEnd 字段来测试是否可以从输入流中读取下一个头部信息
 92             if (header.nameEnd == 0) {
 93                 if (header.valueEnd == 0) {
 94                     return;
 95                 } else {
 96                     throw new ServletException(
 97                             sm.getString("httpProcessor.parseHeaders.colon"));
 98                 }
 99             }
100
101             String name = new String(header.name, 0, header.nameEnd);
102             String value = new String(header.value, 0, header.valueEnd);
103
104             //将获取到的请求头的名称和值放入HttpRquest对象中
105             //如名称可以为content-length,值可以为10164(某个数字)
106             request.addHeader(name, value);
107
108             //判断是否是cookie(cookie包含在请求头中),格式如
109 //Cookie: BD_UPN=1d314753; ispeed_lsm=4; sugstore=1; BAIDUID=3E664426E867095427DD59:FG=1; BIDUPSID=3E664426E827DD59; PSTM=1440774226; BDUSS=Ex4NkJ0bEF0WTgwMwAAAA; ATS_PSID=1
110             if (name.equals("cookie")) {
111                 //如果是cookie,还要对cookie做特殊处理,本文后面讲解
112                 Cookie cookies[] = RequestUtil.parseCookieHeader(value);
113                 for (int i = 0; i < cookies.length; i++) {
114                     if (cookies[i].getName().equals("jsessionid")) {
115                         // Override anything requested in the URL
116                         if (!request.isRequestedSessionIdFromCookie()) {
117                             // Accept only the first session id cookie
118                             request.setRequestedSessionId(cookies[i].getValue());
119                             request.setRequestedSessionCookie(true);
120                             request.setRequestedSessionURL(false);
121                         }
122                     }
123                     request.addCookie(cookies[i]);
124                 }
125                 //判断请求中是否有content-length
126             } else if (name.equals("content-length")) {
127                 int n = -1;
128                 try {
129                     //有的话直接转为int类型保存到HttpRequest对象中
130                     n = Integer.parseInt(value);
131                 } catch (Exception e) {
132                     throw new ServletException(
133                             sm.getString("httpProcessor.parseHeaders.contentLength"));
134                 }
135                 request.setContentLength(n);
136             } else if (name.equals("content-type")) {
137                 //如果是content-type直接保存
138                 request.setContentType(value);
139             }
140         } // end while
141     }
142
143     //解析请求行
144     //请求行如:GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1
145     private void parseRequest(SocketInputStream input, OutputStream output)
146             throws IOException, ServletException {
147
148         //从SocketInputStream对象中直接获取RequestLine对象
149         input.readRequestLine(requestLine);
150
151         //获取请求的方式:如GET
152         String method = new String(requestLine.method, 0, requestLine.methodEnd);
153         //这里没有直接获取请求的URI
154         //因为如/myApp/ModernServlet?userName=tarzan&password=pwd后面有查询的字符串,需要先分割
155         String uri = null;
156         //获取请求的协议版本:如HTTP/1.1
157         String protocol = new String(requestLine.protocol, 0,
158                 requestLine.protocolEnd);
159
160         //请求行无效的情况:没有请求的方式或没有请求的URI
161         if (method.length() < 1) {
162             throw new ServletException("Missing HTTP request method");
163         } else if (requestLine.uriEnd < 1) {
164             throw new ServletException("Missing HTTP request URI");
165         }
166         //判断和获取请求行中第二项中的请求参数,并获取到URI
167         int question = requestLine.indexOf("?");
168         if (question >= 0) {//有参数的
169             //得到"?"后面的查询字符串:如userName=tarzan&password=pwd,并保存到HttpRequest对象中
170             request.setQueryString(new String(requestLine.uri, question + 1,
171                     requestLine.uriEnd - question - 1));
172             //得到URI
173             uri = new String(requestLine.uri, 0, question);
174         } else {
175             //没参数的
176             request.setQueryString(null);
177             uri = new String(requestLine.uri, 0, requestLine.uriEnd);
178         }
179
180         //这里的if语句用于请求的不是以/开头的相对资源,
181         //即获取以绝对地址的请求方式的URI
182         if (!uri.startsWith("/")) {
183             int pos = uri.indexOf("://");
184             // Parsing out protocol and host name
185             if (pos != -1) {
186                 pos = uri.indexOf(‘/‘, pos + 3);
187                 if (pos == -1) {
188                     uri = "";
189                 } else {
190                     uri = uri.substring(pos);
191                 }
192             }
193         }
194
195         //检查并解析第二项中的可能存在的 jsessionid
196         String match = ";jsessionid=";
197         int semicolon = uri.indexOf(match);
198         if (semicolon >= 0) {
199             String rest = uri.substring(semicolon + match.length());
200             int semicolon2 = rest.indexOf(‘;‘);
201             if (semicolon2 >= 0) {
202                 //将获取到的值放到HttpRequest对象中
203                 request.setRequestedSessionId(rest.substring(0, semicolon2));
204                 rest = rest.substring(semicolon2);
205             } else {
206                 request.setRequestedSessionId(rest);
207                 rest = "";
208             }
209             //当 jsessionid 被找到,也意味着会话标识符是携带在查询字符串里边,而不是在 cookie里边。需要传递true
210             request.setRequestedSessionURL(true);
211             uri = uri.substring(0, semicolon) + rest;
212         } else {
213             request.setRequestedSessionId(null);
214             request.setRequestedSessionURL(false);
215         }
216
217         //用于纠正“异常”的 URI。
218         String normalizedUri = normalize(uri);
219
220         // Set the corresponding request properties
221         ((HttpRequest) request).setMethod(method);
222         request.setProtocol(protocol);
223         if (normalizedUri != null) {
224             ((HttpRequest) request).setRequestURI(normalizedUri);
225         } else {
226             ((HttpRequest) request).setRequestURI(uri);
227         }
228
229         if (normalizedUri == null) {
230             throw new ServletException("Invalid URI: " + uri + "‘");
231         }
232     }
233
234     //纠正“异常”的 URI。例如,任何\的出现都会给/替代。
235     //这里涉及到URL的编码解码:编码的格式为:%加字符的ASCII码的十六进制表示
236     //如果URL不能纠正返回null,否则返回相同的或者被纠正后的 URI
237     protected String normalize(String path) {
238         if (path == null)
239             return null;
240         // Create a place for the normalized path
241         String normalized = path;
242
243         //如果URI是/~开头的,除去URI前面前四个字符并加上/~
244         //%7E->~
245         if (normalized.startsWith("/%7E") || normalized.startsWith("/%7e"))
246             normalized = "/~" + normalized.substring(4);
247
248         //下面是解码后对应的结果,这些字符不能在URI中出现
249         //%25->%   %2F->/  %2E->.  %5c->250         //如果找到如下字符的其中一个,说明URI错误
251         if ((normalized.indexOf("%25") >= 0)
252                 || (normalized.indexOf("%2F") >= 0)
253                 || (normalized.indexOf("%2E") >= 0)
254                 || (normalized.indexOf("%5C") >= 0)
255                 || (normalized.indexOf("%2f") >= 0)
256                 || (normalized.indexOf("%2e") >= 0)
257                 || (normalized.indexOf("%5c") >= 0)) {
258             return null;
259         }
260         //如果URI仅仅只是/.则返回/
261         //如www.cnblogs.com/.是可以纠正的
262         if (normalized.equals("/."))
263             return "/";
264
265         //将\转为/,这里的\\是指\,第一个\是转义字符
266         if (normalized.indexOf(‘\\‘) >= 0)
267             normalized = normalized.replace(‘\\‘, ‘/‘);
268         //URI字符串如果没有以/开头就加给它
269         if (!normalized.startsWith("/"))
270             normalized = "/" + normalized;
271         //如果存在//,将剩下/
272         //如http://www.cnblogs.com/lzb1096101803/p//4797948.html变为
273         //http://www.cnblogs.com/lzb1096101803/p/4797948.html
274         while (true) {
275             int index = normalized.indexOf("//");
276             if (index < 0)
277                 break;
278             normalized = normalized.substring(0, index)
279                     + normalized.substring(index + 1);
280         }
281         //如果存在/./,变成/
282         while (true) {
283             int index = normalized.indexOf("/./");
284             if (index < 0)
285                 break;
286             normalized = normalized.substring(0, index)
287                     + normalized.substring(index + 2);
288         }
289         //如果存在/../
290         while (true) {
291             int index = normalized.indexOf("/../");
292             if (index < 0)
293                 break;
294             if (index == 0)
295                 return (null); // Trying to go outside our context
296             int index2 = normalized.lastIndexOf(‘/‘, index - 1);
297             normalized = normalized.substring(0, index2)
298                     + normalized.substring(index + 3);
299         }
300         //URI中存在/...或者3个点以上,认为不能纠正
301         if (normalized.indexOf("/...") >= 0)
302             return (null);
303
304         //返回修改后的URI
305         return (normalized);
306
307     }
308
309 }

HttpProcessor.java详细说明:

(其实我更推荐看我注释的代码结合github上的源码进行分析,下面是书上的文字解释,可以帮助大家了解整个流程)

1.解析请求行

  HttpProcessor 的 process 方法调用私有方法 parseRequest 用来解析请求行例如一个 HTTP请求的第一行。这里是一个请求行的例子:GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1请求行的第二部分是 URI 加上一个查询字符串。在上面的例子中,URI 是这样的:/myApp/ModernServlet
另外,在问好后面的任何东西都是查询字符串。因此,查询字符串是这样的:userName=tarzan&password=pwd查询字符串可以包括零个或多个参数。在上面的例子中,有两个参数名 / 值对,userName/tarzan 和 password/pwd。在 servlet/JSP 编程中,参数名 jsessionid 是用来携带一个会话标识符。会话标识符经常被作为 cookie 来嵌入,但是程序员可以选择把它嵌入到查询字符串去,例如,当浏览器的 cookie 被禁用的时候。当 parseRequest 方法被 HttpProcessor 类的 process 方法调用的时候,request 变量指向一个 HttpRequest 实例。parseRequest 方法解析请求行用来获得几个值并把这些值赋给HttpRequest 对象。

  parseRequest 方法首先调用 SocketInputStream 类的 readRequestLine 方法,在这里 requestLine 是 HttpProcessor 里边的 HttpRequestLine 的一个实例.调用它的 readRequestLine 方法来告诉 SocketInputStream 去填入 HttpRequestLine 实例。接下去,parseRequest 方法获得请求行的方法,URI 和协议.不过,在 URI 后面可以有查询字符串,假如存在的话,查询字符串会被一个问好分隔开来。因此,parseRequest 方法试图首先获取查询字符串。并调用 setQueryString 方法来填充HttpRequest 对象.

  大多数情况下,URI 指向一个相对资源,URI 还可以是一个绝对值

  然后,查询字符串也可以包含一个会话标识符,用 jsessionid 参数名来指代。因此,parseRequest 方法也检查一个会话标识符。假如在查询字符串里边找到 jessionid,方法就取得会话标识符,并通过调用 setRequestedSessionId 方法把值交给 HttpRequest 实例当 jsessionid 被找到,也意味着会话标识符是携带在查询字符串里边,而不是在 cookie里边。因此,传递 true 给 request 的 setRequestSessionURL 方法。否则,传递 false 给setRequestSessionURL 方法并传递 null 给 setRequestedSessionURL 方法。

  到这个时候,uri 的值已经被去掉了 jsessionid。接下去,parseRequest 方法传递 uri 给 normalize 方法,用于纠正“异常”的 URI。例如,任何\的出现都会给/替代。假如 uri 是正确的格式或者异常可以给纠正的话, normalize 将会返回相同的或者被纠正后的 URI。假如 URI 不能纠正的话,它将会给认为是非法的并且通常会返回null。在这种情况下(通常返回 null),parseRequest 将会在方法的最后抛出一个异常。

2.解析头部

  parseHeaders 方法包括一个 while 循环用于持续的从 SocketInputStream 中读取头部,直到再也没有头部出现为止。循环从构建一个 HttpHeader 对象开始,并把它传递给类SocketInputStream 的 readHeader 方法.然后,你可以通过检测 HttpHeader 实例的 nameEnd 和 valueEnd 字段来测试是否可以从输入流中读取下一个头部信息

  一旦你获取到头部的名称和值,你通过调用 HttpRequest 对象的 addHeader 方法来把它加入
headers 这个 HashMap 中。

  些头部也需要某些属性的设置。例如,当 servlet 调用 javax.servlet.ServletRequest的 getContentLength 方法的时候, content-length 头部的值将被返回。而包含 cookies 的 cookie头部将会给添加到 cookie 集合中。

 

RequestUtil.java解析cookie

这里只贴上解析cookie的相关代码

cookie格式如下:Cookie: userName=budi; password=pwd

只是简单的切割字符串然后将key value放入一个cookie对象中,因为不是很关键的代码,所以不展开代码了,否则篇幅太多

至于如何切割的,可以自己将这句“userName=budi;password=pwd”进行调试

 1    public static Cookie[] parseCookieHeader(String header) {
 2
 3         if ((header == null) || (header.length() < 1))
 4             return (new Cookie[0]);
 5
 6         ArrayList cookies = new ArrayList();
 7         while (header.length() > 0) {
 8             int semicolon = header.indexOf(‘;‘);
 9             if (semicolon < 0)
10                 semicolon = header.length();
11             if (semicolon == 0)
12                 break;
13             String token = header.substring(0, semicolon);
14             if (semicolon < header.length())
15                 header = header.substring(semicolon + 1);
16             else
17                 header = "";
18             try {
19                 int equals = token.indexOf(‘=‘);
20                 if (equals > 0) {
21                     String name = token.substring(0, equals).trim();
22                     String value = token.substring(equals+1).trim();
23                     cookies.add(new Cookie(name, value));
24                 }
25             } catch (Throwable e) {
26                 ;
27             }
28         }
29
30         return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));
31
32     }

SocketInputStream.java

上一节,你没有试图为那两个应用程序去进一步解析请求。org.apache.catalina.connector.http.SocketInputStream 提供的方法不
仅用来获取请求行,还有请求头部。通过传递一个 InputStream 和一个指代实例使用的缓冲区大小的整数,来构建一个
SocketInputStream 实例。

将在下一节具体说明。

最后我们了解一下StringManager类,这个类在整个Catalina项目中有着重要的作用(用来处理错误信息),对于本节的应用,可以在ex03.pyrmont.connector.http.HttpProcessor中找到这个类的实例。

一个像 Tomcat 这样的大型应用需要仔细的处理错误信息。在 Tomcat 中,错误信息对于系统管理员和 servlet 程序员都是有用的。例如,Tomcat 记录错误信息,让系统管理员可以定位发生的任何异常。对 servlet 程 序 员 来 说 , Tomcat 会在抛出的任何一个javax.servlet.ServletException 中发送一个错误信息,这样程序员可以知道他 servlet究竟发送什么错误了。Tomcat 所采用的方法是在一个属性文件里边存储错误信息,这样,可以容易的修改这些信息。

看看StringManager类,因为篇幅有限,下面是部分核心代码。

代码中的注释有助于理解。

 1 package org.apache.catalina.util;
 2
 3 import java.util.Hashtable;
 4 import java.util.MissingResourceException;
 5 import java.util.ResourceBundle;
 6
 7 public class StringManager {
 8
 9     private ResourceBundle bundle;
10
11     //设为private,单例模式的特点,要获取对象,可以通过getManager(String)方法
12     //就是在这里获取了实例对应包下的bundle对象。
13     //格式如org.apache.catalina.util.packageName.LocalStrigns
14     private StringManagerMy(String packageName) {
15         String bundleName = packageName + ".LocalStrings";
16         bundle = ResourceBundle.getBundle(bundleName);
17     }
18
19     //StringManager类实例的包下properties文件中等号左边的值作为Key传递
20     //返回的是等号右面的信息
21     public String getString(String key) {
22         if (key == null) {
23             String msg = "key is null";
24
25             throw new NullPointerException(msg);
26         }
27         String str = null;
28         try {
29             str = bundle.getString(key);
30         } catch (MissingResourceException mre) {
31             str = "Cannot find message associated with key ‘" + key + "‘";
32         }
33         return str;
34     }
35
36     //保存不同包下的StringManager对象
37     private static Hashtable managers = new Hashtable();
38
39     //单例模式用这个方法获取不同包的StringManager对象,
40     //因为可能同时被多个类使用产生错误,所以方法需要设置为同步
41     public synchronized static StringManager getManager(String packageName) {
42         StringManager mgr = (StringManager) managers.get(packageName);
43         if (mgr == null) {
44             mgr = new StringManager(packageName);
45             managers.put(packageName, mgr);
46         }
47         return mgr;
48     }
49 }

StringManager详细说明:

Tomcat有数以百计的类,如果把所有的类的错误信息都保存在某个大的属性文件中,将导致很严重的维护问题。为了避免这一情况,Tomcat 为每个包都分配一个属性文件。例如,在包 org.apache.catalina.connector 里边的属性文件包含了该包所有的类抛出的所有错误信息。每个属性文件都会被一个 org.apache.catalina.util.StringManager 类的实例所处理。相同包里边的许多类可能也需要 StringManager,为每个对象创建一个 StringManager 实例是一种资源浪费。StringManager类采用了单例模式,同一包下使用同一StringManager对象即可,放入StringManager类的managers实例中,不同包才使用不同的对象。

当Tomcat 运行时,将会有许多 StringManager 实例,每个实例会读取这个实例对应包下的一个properties属性文件。此外,由于 Tomcat 的受欢迎程度,提供多种语言的错误信息也是有意义的。目前,有三种语言是被支持的。英语的错误信息属性文件名为 LocalStrings.properties。另外两个是西班牙语和日语,分别放在 LocalStrings_es.properties 和 LocalStrings_ja.properties 里边。如果对Bundle实现国际化使用不熟悉的,可以自行查阅相关资料。

没有展示的方法都是getString的重载方法,可以在org.apache.catalina.util.Manager中找到这个类查看具体实现。

在ex03.pyrmont.connector.http.HttpProcessor.java中有这样几段

protected StringManager sm = StringManager
            .getManager("ex03.pyrmont.connector.http");
throw new ServletException(
        sm.getString("httpProcessor.parseHeaders.colon"));

在ex03.pyrmont.connector.http.LocalStrings.properties中对应的有

httpProcessor.parseHeaders.colon=Invalid HTTP header format

说了那么多,大家应该可以理解了吧。

附:

相应代码可以在我的github上找到下载,拷贝到eclipse,然后打开对应包的代码即可。

https://github.com/zebinlin/Tomcat4-src

如发现编译错误,可能是由于jdk不同版本对编译的要求不同导致的,可以不管,供学习研究使用。

时间: 2024-10-12 21:44:07

Tomcat剖析(三):连接器的相关文章

Tomcat剖析(四):Tomcat默认连接器(2)

Tomcat剖析(四):Tomcat默认连接器(2) 目录: Tomcat剖析(一):一个简单的Web服务器 Tomcat剖析(二):一个简单的Servlet服务器 Tomcat剖析(三):连接器(1) Tomcat剖析(三):连接器(2) Tomcat剖析(四):Tomcat默认连接器(1) Tomcat剖析(四):Tomcat默认连接器(2) 同样的,你需要先下载代码. https://github.com/zebinlin/Tomcat4-src 经过上一节的讲解,你已经理解了请求和响应对

Tomcat剖析(五):Tomcat 容器

Tomcat剖析(五):Tomcat 容器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三):连接器(1) 4. Tomcat剖析(三):连接器(2) 5. Tomcat剖析(四):Tomcat默认连接器(1) 6. Tomcat剖析(四):Tomcat默认连接器(2) 7. Tomcat剖析(五):容器 第一部分:概述 这一节基于<深度剖析Tomcat>第五章:容器 总结而成 一定要先到我的git

Tomcat剖析(二)

Tomcat剖析(二) 目录: Tomcat剖析(一):一个简单的Web服务器 Tomcat剖析(二):一个简单的Servlet服务器 这一节基于 <深度剖析Tomcat>第二章: 一个简单的Servlet服务器 总结而成. 上一节,我们了解了一个简单的Web服务器的总体处理流程是怎样的:这一节,我们开始搭建一个简单的Servlet容器,也就是增加实现了servlet的简单加载执行,而不仅仅是将文件内容输出到浏览器上.当然,这一节的servlet的实现是最简单的,用来了解整个Servlet的大

Tomcat剖析(一)

Tomcat剖析(一) 这一节基于 <深度剖析Tomcat>第一章:一个简单的Web服务器 总结而成.写得不好之处,请见谅 对Tomcat而言,如果直接对其源码进行分析是困难的,所以这里被设计得足够简单使得你能理解一个 servlet 容器是如何工作的,没有对Tomcat本身的连接器和容器进行分析,本节旨在明白服务器的整个流程大致是如何进行的.需要知道如何完善Web服务器,可以参考这本书后面的章节,或者等我发表<Tomcat剖析(二)>. 文中细说明是从书中相关章节中copy下来的

javaweb学习总结十八(软件密码学、配置tomcat的https连接器以及tomcat管理平台)

一:软件密码学 1:对称加密 对称加密是最快速.最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key).对称加密有很多种算法,由于它效率很高,所以被广泛使用在很多加密协议的核心当中. 对称加密通常使用的是相对较小的密钥,一般小于256 bit.因为密钥越大,加密越强,但加密与解密的过程越慢.如果你只用1 bit来做这个密钥,那黑客们可以先试着用0来解密,不行的话就再用1解:但如果你的密钥有1 MB大,黑客们可能永远也无法破解,但加

将项目部署到tomcat的三种方法

2014年8月初8    星期五 又到星期五了,可以轻松的敲点东西,不担心睡太晚起不来,或者起来后上班打瞌睡. 由于近期学习了solr相关的知识,部署solr项目到tomcat上的方式很多,包括官方的等.我自己也找了中非官方的方法,感觉很好.而且以前用MyEclipse开发,出错很少,现在用Eclipse所以部署项目后在后台调用user.dir的时候出错,试了一下午也没事出来,而且越试越想试,但是带我的人不让我弄了,他帮我弄,让我干其他任务,我都怀疑是不是他也和我一样就喜欢干这种事情,所以自己去

libevent源码深度剖析三

libevent源码深度剖析三 --libevent基本使用场景和事件流程张亮 1 前言 学习源代码该从哪里入手?我觉得从程序的基本使用场景和代码的整体处理流程入手是个不错的方法,至少从个人的经验上讲,用此方法分析libevent是比较有效的. 2 基本应用场景 基本应用场景也是使用libevnet的基本流程,下面来考虑一个最简单的场景,使用livevent设置定时器,应用程序只需要执行下面几个简单的步骤即可. 1)首先初始化libevent库,并保存返回的指针struct event_base

Apache HTTP Server 与 Tomcat 的三种连接方式介绍

Apache HTTP Server 与 Tomcat 的三种连接方式介绍 整合 Apache Http Server 和 Tomcat 可以提升对静态文件的处理性能.利用 Web 服务器来做负载均衡以及容错.无缝的升级应用程序.本文介绍了三种整合 Apache 和 Tomcat 的方式. 3 评论: 刘 冬 ([email protected]), 开发工程师, 2007 年 1 月 15 日 内容 首先我们先介绍一下为什么要让 Apache 与 Tomcat 之间进行连接.事实上 Tomca

boost.asio源码剖析(三) ---- 流程分析

* 常见流程分析之一(Tcp异步连接) 我们用一个简单的demo分析Tcp异步连接的流程: 1 #include <iostream> 2 #include <boost/asio.hpp> 3 4 // 异步连接回调函数 5 void on_connect(boost::system::error_code ec) 6 { 7 if (ec) // 连接失败, 输出错误码 8 std::cout << "async connect error:"