《How Tomcat Works》读书笔记(三)Connector

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。

  • HttpConnector:等待http请求、构建HttpProcessor,并把请求的socket转交给HttpProcessor
  • HttpProcessor:生成request、response对象,传递给ServletProcessor或StaticResourceProcessor
  • HttpRequest  :implements javax.servlet.http.HttpServletRequest
  • HttpResponse:implements javax.servlet.http.HttpServletResponse
Bootstrap
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请求的第一行:
GET /myApp/ModernServlet? userName=tarzan&password=pw d  HTTP/1.1 
绿色部分叫做query string

我们再来看一个HTTP请求对应的类:
这一行对应的类就是RequestLine
它下面的请求头对应的类就是HttpHeader

HttpProcessor
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,如:
content-length header
cookie 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和Http request body中。我们都要进行检查。
//解析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");
    }
}



你可能感兴趣的:(tomcat)