Chapter 6 explains the Lifecycle interface. This interface defines the lifecycle of a Catalina component and provides an elegant way of notifying other components of events that occur in that component. In addition, the Lifecycle interface provides an elegant mechanism for starting and stopping all the components in Catalina by one single start/stop.
public Lifecycle getLifecycle(){ return (this.lifecycle); }
public String getType(){ return (this.type); } }
The LifecycleListener Interface
1 2 3 4 5 6 7 8
publicinterfaceLifecycleListener{ /** * Acknowledge the occurrence of the specified event. * * @param event LifecycleEvent that has occurred */ publicvoidlifecycleEvent(LifecycleEvent event); }
The LifecycleSupport Class
这个 support 类内部声明了一个 Array 变量存储要操作的 Listener: private LifecycleListener listeners[] = new LifecycleListener[0];. 主就三个方法:
publicinterfaceLifecycleListener{ /** * Acknowledge the occurrence of the specified event. * * @param event LifecycleEvent that has occurred */ publicvoidlifecycleEvent(LifecycleEvent event); }
System.out.println("--------------- handle A ---------------"); handlerA.handleRequest("A"); System.out.println("--------------- handle B ---------------"); handlerA.handleRequest("B"); System.out.println("--------------- handle Z ---------------"); handlerA.handleRequest("Z"); } }
// 执行结果 // // --------------- handle A --------------- // Concrete Handler A processed... // --------------- handle B --------------- // Concrete Handler A can't process, call other handler... // Concrete Handler B processed... // --------------- handle Z --------------- // Concrete Handler A can't process, call other handler... // Concrete Handler B can't process, call other handler... // Concrete handler z processed...
AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR); AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG); AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
System.out.println("--------------- handle info ---------------"); loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
System.out.println("--------------- handle debug ---------------"); loggerChain.logMessage(AbstractLogger.DEBUG, "This is a debug level information.");
System.out.println("--------------- handle Error ---------------"); loggerChain.logMessage(AbstractLogger.ERROR, "This is an error information."); } }
// --------------- handle info --------------- // Standard Console::Logger: This is an information. // --------------- handle debug --------------- // File::Logger: This is a debug level information. // Standard Console::Logger: This is a debug level information. // --------------- handle Error --------------- // Error Console::Logger: This is an error information. // File::Logger: This is an error information. // Standard Console::Logger: This is an error information.
Chapter 5 discusses the container module. A container is represented by the org.apache.catalina.Container interface and there are four types of containers: engine, host, context, and wrapper. This chapter offers two applications that work with contexts and wrappers.
public Container map(Request request, boolean update){ //this method is taken from the map method in org.apache.cataline.core.ContainerBase //the findMapper method always returns the default mapper, if any, regardless the //request's protocol Mapper mapper = findMapper(request.getRequest().getProtocol()); if (mapper == null) return (null);
// Use this Mapper to perform this mapping return (mapper.map(request, update)); }
Chapter4 presents Tomcat 4’s default connector. This connector has been deprecated in favor of a faster connector called Coyote. Nevertheless, the default connector is simpler and easier to understand.
publicvoidstart()throws LifecycleException { // Validate and update our current state if (started) thrownew LifecycleException (sm.getString("httpConnector.alreadyStarted")); threadName = "HttpConnector[" + port + "]"; lifecycle.fireLifecycleEvent(START_EVENT, null); started = true;
HttpConnector 的主要逻辑都在 run 方法中,该方法通过 while 循环等待发送过来的响应,直到服务器停止。
1 2 3 4 5 6 7 8 9 10 11 12 13
publicvoidrun(){ // Loop until we receive a shutdown command while (!stopped) { // Accept the next incoming connection from the server socket Socket socket = null; try { socket = serverSocket.accept(); // ... } // ... } // ... }
For each HttpProcessor instance the HttpConnector creates, its start method is called, effectively starting the “processor thread” of the HttpProcessor instance.
/** * Create and return a new processor suitable for processing HTTP * requests and returning the corresponding responses. */ private HttpProcessor newProcessor(){
// if (debug >= 2) // log("newProcessor: Creating new processor"); HttpProcessor processor = new HttpProcessor(this, curProcessors++); if (processor instanceof Lifecycle) { try { ((Lifecycle) processor).start(); } catch (LifecycleException e) { log("newProcessor", e); return (null); } } created.addElement(processor); return (processor);
/** * Process an incoming TCP/IP connection on the specified socket. Any * exception that occurs during processing must be logged and swallowed. * <b>NOTE</b>: This method is called from our Connector's thread. We * must assign it to our own thread so that multiple simultaneous * requests can be handled. * * @param socket TCP socket to process */ synchronizedvoidassign(Socket socket){
// Wait for the Processor to get the previous Socket while (available) { try { wait(); } catch (InterruptedException e) { } }
// Store the newly available Socket and notify our thread this.socket = socket; available = true; notifyAll();
if ((debug >= 1) && (socket != null)) log(" An incoming request is being assigned");
// Construct and initialize the objects we will need try { input = new SocketInputStream(socket.getInputStream(), connector.getBufferSize()); } catch (Exception e) { log("process.create", e); ok = false; }
接下来是一个 while 循环读取 inputStream 中的数据
1 2
keepAlive = true; while (!stopped && ok && keepAlive) {...}
if (http11) { // Sending a request acknowledge back to the client if // requested. ackRequest(output); // If the protocol is HTTP/1.1, chunking is allowed. if (connector.isChunkingAllowed()) response.setAllowChunking(true); }
// Ask our Container to process this request try { ((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate()); if (ok) { connector.getContainer().invoke(request, response); } }
// Finish up the handling of the request if (finishResponse) { response.finishResponse(); request.finishRequest(); output.flush(); ok = false; }
最后检查 Connection 的值并置位,回收 request 和 response
1 2 3 4 5 6 7 8 9 10 11 12 13
// We have to check if the connection closure has been requested // by the application or the response stream (in case of HTTP/1.0 // and keep-alive). if ( "close".equals(response.getHeader("Connection")) ) { keepAlive = false; }
// End of request processing status = Constants.PROCESSOR_IDLE;
// Recycling the request and the response objects request.recycle(); response.recycle();
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
Chapter2 explains how simple servlet containers work. This chapter comes with two servlet container applications that can service requests for static resources as well as very simple servlets. In particular, you will learn how you can create request and response objects and pass them to the requested servlet’s service method. There is also a servlet that can be run inside the servlet containers and that you can invoke from a web browser.
publicclassHttpServer1{ publicstaticvoidmain(String[] args){ HttpServer1 server = new HttpServer1(); server.await(); }
publicvoidawait(){ ServerSocket serverSocket = null; int port = 8080; serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
// Loop waiting for a request while (!shutdown) { Socket socket = serverSocket.accept(); InputStream input = socket.getInputStream(); OutputStream 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 { StaticResourceProcessor 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); } } }
// 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 class. urls[0] = new URL(null, repository, streamHandler); URLClassLoader loader = new URLClassLoader(urls);