3.1 StringManager
为啥要先讲StringManager呢?
话说tomcat算是一个大型项目了(最新的tomcat7大概有35W行代码),因此处理报错信息就要狠小心,因为报错信息为开发者和系统管理员提供有用的线索。
tomcat把错误信息保存在properties文件中,项目又太大,不能只用一个文件,tomcat为每个需要错误信息的package都提供了一个properties文件。
每一个properties文件都是有一个StringManager的实例负责处理。于是tomcat启动后会存在很多StringManager实例。
每个package中的类共享一个StringManager实例。
public class StringManager {
//private构造器,单例
private StringManager(String packageName) {
...
}
//包名---StringManager实例映射
private static Hashtable<String, StringManager> managers =
new Hashtable<String, StringManager>();
//考虑了多线程安全,根据包名返回一个StringManager实例 public static final synchronized StringManager getManager(String packageName) {
StringManager mgr = managers.get(packageName);
if (mgr == null) {
mgr = new StringManager(packageName);
managers.put(packageName, mgr);
}
return mgr;
}
//根据errorCode拿到errorMessage
//e.g. httpConnector.alreadyInitialized=HTTP connector has already been initialized
public String getString(String key) {
...
}
}
3.2 模块化
分为三个模块:connector , startup , core .
startup 模块只有一个类Bootstrap, 负责启动应用
connector 模块包含HttpConnector,HttpProcessor,HttpRequest和HttpResponse
core 模块包含ServletProcessor和StaticResourceProcessor
我们把《How Tomcat Works》读书笔记(二)中的HttpServer拆分成两个类:HttpConnector和HttpProcessor,把Request改成HttpRequest,Response改成HttpResponse。
public final class Bootstrap { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); //启动连接器 connector.start(); } }
HttpConnector
import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; /** *http连接器 **/ public class HttpConnector implements Runnable { //停止flag 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) { // 等待下一个到达ServerSocket的连接 Socket socket = null; try { socket = serverSocket.accept(); }catch (Exception e){ continue; } // 把socket转交给HttpProcessor HttpProcessor processor = new HttpProcessor(this); processor.process(socket); } } public void start(){ Thread thread = new Thread(this); thread.start (); } }我们再来看一下HTTP请求的第一行:
public class HttpProcessor{ HttpRequest request; HttpResponse response; private HttpRequestLine requestLine = new HttpRequestLine(); public void process(Socket socket) { SocketInputStream input = null; OutputStream output = null; try { input = new SocketInputStream(socket.getInputStream(), 2048); output = socket.getOutputStream(); request = new HttpRequest(input); response = new HttpResponse(output); response.setRequest(request); response.setHeader("Server", "Pyrmont Servlet Container"); parseRequest(input, output); parseHeaders(input); if (request.getRequestURI().startsWith("/servlet/")) { ServletProcessor processor = new ServletProcessor(); processor.process(request, response); }else { StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } socket.close(); } catch (Exception e) { e.printStackTrace (); } } //解析RequestLine private void parseRequest(SocketInputStream input, OutputStream output){...} //解析Headers private void parseHeaders(SocketInputStream input,OutputStream output){...} }
3.2.1 Parsing RequestLine
//从SocketInputStream中读入requestLine input.readRequestLine(requestLine);
//获取http方法 String method = new String(requestLine.method, 0, requestLine.methodEnd); //获取协议 String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
//获取queryString并从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); }
//检查URI是不是绝对路径(with the HTTP protocol),如果是把URI转化成相对路径 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); } } }
//检查jsessionid是否存在,如果存在,设置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); }
// 纠正URI的错误(如把¥替换成/) String normalizedUri = normalize(uri); // Set the corresponding request properties ((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 + "'"); }3.2.2 Parsing Headers
//构造一个HttpHeader HttpHeader header = new HttpHeader();
//从SocketInputStream中读出下一个Header input.readHeader(header);
//判断下一个header是否存在 if (header.nameEnd == 0) { if (header.valueEnd == 0) { return; }else { throw new ServletException(sm.getString("httpProcessor.parseHeaders.colon")); } }
//获取下一个header的name、value String name = new String(header.name, 0, header.nameEnd); String value = new String(header.value, 0, header.valueEnd);
//加入到HttpRequest的HashMap<String,String> headers中 request.addHeader(name, value);除了普通的header还存在一些需要特殊处理的header,如:
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); }
3.2.3 Parsing Cookies
下面看一个cookie header的示例 Cookie: userName=budi; password=pwd;
解析cookie用的是org.apache.catalina.util.RequestUtil这个类:
public static Cookie[] parseCookieHeader(String header){ if ((header == null) || (header.length 0 < 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中我们这样就使用它来解析cookie:
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]); } }3.2.4 Obtaining Parameters
//解析queryString中的参数 String queryString = getQueryString(); try { RequestUtil.parseParameters(results, queryString, encoding); } catch (UnsupportedEncodingException e) { ; }
//解析request body中的参数 String contentType = getContentType(); if (contentType == null) contentType = ""; int semicolon = contentType.indexOf(';'); if (semicolon >= 0) { contentType = contentType.substring (0, semicolon).trim(); } else { contentType = contentType.trim(); } if ("POST".equals(getMethod()) && (getContentLength() > 0) && "application/x-www-form-urlencoded".equals(contentType)) { try { int max = getContentLength(); int len = 0;byte buf[] = new byte[getContentLength()]; ServletInputStream is = getInputStream(); while (len < max) { int next = is.read(buf, len, max - len); if (next < 0 ) { break; } len += next; } is.close(); if (len < max) { throw new RuntimeException("Content length mismatch"); } RequestUtil.parseParameters(results, buf, encoding); } catch (UnsupportedEncodingException ue) { ; } catch (IOException e) { throw new RuntimeException("Content read fail"); } }