Chapter3 presents a simplified version of Tomcat 4’s default connector. The application built in this chapter serves as a learning tool to understand the connector discussed in Chapter4
为了节省资源,StringManager 内部通过 Hashtable 存储多语言,并通过单例模式创建这个 field
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
privatestatic Hashtable managers = new Hashtable();
/** * Get the StringManager for a particular package. If a manager for * a package already exists, it will be reused, else a new * StringManager will be created and returned. * * @param packageName */
privatevoidparseRequest(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) { thrownew ServletException("Missing HTTP request method"); } elseif (requestLine.uriEnd < 1) { thrownew 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); }
// Normalize URI (using String operations at the moment) 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) { thrownew ServletException("Invalid URI: " + uri + "'"); } }
UML 图示如下
Request line 的类实现为 HttpRequestLine, 它的实现比较有意思,它为这个类中的各个部分声明了一个存储的 char 数组,并标识了结束地址 char[] method, int methodEnd
我们通过处理 SocketInputStream 可以得到 request line 的信息用以填充 HttpRequestLine,主要涉及的方法
privatevoidparseHeaders(SocketInputStream input)throws IOException, ServletException { while (true) { HttpHeader header = new HttpHeader(); ;
// Read the next header input.readHeader(header); if (header.nameEnd == 0) { if (header.valueEnd == 0) { return; } else { thrownew ServletException( sm.getString("httpProcessor.parseHeaders.colon")); } }
String name = new String(header.name, 0, header.nameEnd); String value = new String(header.value, 0, header.valueEnd); request.addHeader(name, value); // do something for some headers, ignore others. if (name.equals("cookie")) { 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]); } } elseif (name.equals("content-length")) { int n = -1; try { n = Integer.parseInt(value); } catch (Exception e) { thrownew ServletException( sm.getString("httpProcessor.parseHeaders.contentLength")); } request.setContentLength(n); } elseif (name.equals("content-type")) { request.setContentType(value); } } // end while }
/** * Parse a cookie header into an array of cookies according to RFC 2109. * * @param header Value of an HTTP "Cookie" header */ publicstatic 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) { ; } }
/** * Parse the parameters of this request, if it has not already occurred. * If parameters are present in both the query string and the request * content, they are merged. */ protectedvoidparseParameters(){ if (parsed) return; ParameterMap results = parameters; if (results == null) results = new ParameterMap(); results.setLocked(false); String encoding = getCharacterEncoding(); if (encoding == null) encoding = "ISO-8859-1";
// Parse any parameters specified in the query string String queryString = getQueryString(); try { RequestUtil.parseParameters(results, queryString, encoding); } catch (UnsupportedEncodingException e) { ; }
// Parse any parameters specified in the input stream 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[] = newbyte[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) { thrownew RuntimeException("Content length mismatch"); } RequestUtil.parseParameters(results, buf, encoding); } catch (UnsupportedEncodingException ue) { ; } catch (IOException e) { thrownew RuntimeException("Content read fail"); } }
// Store the final results results.setLocked(true); parsed = true; parameters = results; }
在 GET 类型的 request 中,所有的 parameter 都是存在 URL 中的,在POST 类型的 request,parameter 是存在 body 中的。解析的 parameter 会存在特殊的 Map 中,这个 map 不允许改变存放的 parameter 的值。对应的实现是 org.apache.catalina.util.ParameterMap. 看了一下具体的实现类代码,其实就是一个 HashMap, 最大的特点是他新加了一个 locked 的 boolean 属性,在增删改的时候都会先检查一下这个 flag 如果此时 flag 为 false 则抛异常。
if (locked) thrownew IllegalStateException(sm.getString("parameterMap.locked")); super.clear();
}
// ... }
Creating a HttpResponse Object
HttpReponse 类图
通过设置 PrintWriter 的 auto flush 功能,之前打印的 behavior 才修复了,不然只会打印第一句话。为了了解这里说的东西,你需要查一下 Writer 相关的知识点。
问题
server 启动后访问 URL 抛异常 Exception in thread "Thread-0" java.util.MissingResourceException: Can't find bundle for base name com.jzheng.connector.http.LocalStrings, locale en_US