zoukankan      html  css  js  c++  java
  • Tomcat源码学习(18)How Tomcat works(转)

    请求对象

        默认连接器哩变得HTTP请求对象指代org.apache.catalina.Request接口。这个接口被类RequestBase直接实现了,也是HttpRequest的父接口。最终的实现是继承于HttpRequest的HttpRequestImpl。像第3章一样,有几个facade类:RequestFacade和HttpRequestFacade。Request接口和它的实现类的UML图在Figure 4.2中给出。注意的是,除了属于javax.servlet和javax.servlet.http包的类,前缀org.apache.catalina已经被省略了。

    响应对象

        Response接口和它的实现类的UML图在Figure 4.3中给出。

    处理请求

        到这个时候,你已经理解了请求和响应对象,并且知道HttpConnector对象是如何创建它们的。现在是这个过程的最后一点东西了。在这节中我们关注HttpProcessor类的process方法,它是一个套接字赋给它之后,在HttpProcessor类的run方法中调用的。process方法会做下面这些工作:

    • 解析连接
    • 解析请求
    • 解析头部

        在解释完process方法之后,在本节的各个小节中将讨论每个操作。
        process方法使用布尔变量ok来指代在处理过程中是否发现错误,并使用布尔变量finishResponse来指代Response接口中的finishResponse方法是否应该被调用。

    boolean ok = true;
    boolean finishResponse = true;
    另外,process方法也使用了布尔变量keepAlive,stopped和http11。keepAlive表示连接是否是持久的,stopped表示HttpProcessor实例是否已经被连接器终止来确认process是否也应该停止,http11表示 从web客户端过来的HTTP请求是否支持HTTP 1.1。
        像第3章那样,有一个SocketInputStream实例用来包装套接字的输入流。注意的是,SocketInputStream的构造方法同样传递了从连接器获得的缓冲区大小,而不是从HttpProcessor的本地变量获得。这是因为对于默认连接器的用户而言,HttpProcessor是不可访问的。通过传递Connector接口的缓冲区大小,这就使得使用连接器的任何人都可以设置缓冲大小。
    另外,process方法也使用了布尔变量keepAlive,stopped和http11。keepAlive表示连接是否是持久的,stopped表示HttpProcessor实例是否已经被连接器终止来确认process是否也应该停止,http11表示 从web客户端过来的HTTP请求是否支持HTTP 1.1。
        像第3章那样,有一个SocketInputStream实例用来包装套接字的输入流。注意的是,SocketInputStream的构造方法同样传递了从连接器获得的缓冲区大小,而不是从HttpProcessor的本地变量获得。这是因为对于默认连接器的用户而言,HttpProcessor是不可访问的。通过传递Connector接口的缓冲区大小,这就使得使用连接器的任何人都可以设置缓冲大小。
    SocketInputStream input = null;
    OutputStream output = null;
    // Construct and initialize the objects we will need
    try {
       input = new SocketInputStream(socket.getInputstream(),
       connector.getBufferSize());
    }
    catch (Exception e) {
       ok = false;
    }
        然后,有个while循环用来保持从输入流中读取,直到HttpProcessor被停止,一个异常被抛出或者连接给关闭为止。
    keepAlive = true;
    while (!stopped && ok && keepAlive) {
       ...
    }
        在while循环的内部,process方法首先把finishResponse设置为true,并获得输出流,并对请求和响应对象做些初始化处理。
    在while循环的内部,process方法首先把finishResponse设置为true,并获得输出流,并对请求和响应对象做些初始化处理。
    finishResponse = true;
    try {
       request.setStream(input);
       request.setResponse(response);
       output = socket.getOutputStream();
       response.setStream(output);
       response.setRequest(request);
       ((HttpServletResponse) response.getResponse()).setHeader("Server", SERVER_INFO);
    }
    catch (Exception e) {
       log("process.create", e); //logging is discussed in Chapter 7
       ok = false;
    }
        接着,process方法通过调用parseConnection,parseRequest和parseHeaders方法开始解析前来的HTTP请求,这些方法将在这节的小节中讨论。
    接着,process方法通过调用parseConnection,parseRequest和parseHeaders方法开始解析前来的HTTP请求,这些方法将在这节的小节中讨论。
    try {
       if (ok) {
           parseConnection(socket);
           parseRequest(input, output);
           if (!request.getRequest().getProtocol().startsWith("HTTP/0"))
               parseHeaders(input);
    parseConnection方法获得协议的值,像HTTP0.9, HTTP1.0或HTTP1.1。如果协议是HTTP1.0,keepAlive设置为false,因为HTTP1.0不支持持久连接。如果在HTTP请求里边找到Expect: 100-continue的头部信息,则parseHeaders方法将把sendAck设置为true。
        如果协议是HTTP1.1,并且web客户端发送头部Expect: 100-continue的话,通过调用ackRequest方法它将响应这个头部。它将会测试组块是否是允许的。
    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);
    }
    ackRequest方法测试sendAck的值,并在sendAck为true的时候发送下面的字符串:
    HTTP/1.1 100 Continue\r\n\r\n
        在解析HTTP请求的过程中,有可能会抛出异常。任何异常将会把ok或者finishResponse设置为false。在解析过后,process方法把请求和响应对象传递给容器的invoke方法:
    try {
       ((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate());
       if (ok) {
           connector.getContainer().invoke(request, response);
       }
    }
        接着,如果finishResponse仍然是true,响应对象的finishResponse方法和请求对象的finishRequest方法将被调用,并且结束输出。
    if (finishResponse) {
       ...
       response.finishResponse();
       ...
       request.finishRequest();
       ...
       output.flush();
        while循环的最后一部分检查响应的Connection头部是否已经在servlet内部设为close,或者协议是HTTP1.0.如果是这种情况的话,keepAlive设置为false。同样,请求和响应对象接着会被回收利用。
    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();
    }
        在这个场景中,如果哦keepAlive是true的话,while循环将会在开头就启动。因为在前面的解析过程中和容器的invoke方法中没有出现错误,或者HttpProcessor实例没有被停止。否则,shutdownInput方法将会调用,而套接字将被关闭。
    try {
       shutdownInput(input);
       socket.close();
    }
    ...
        shutdownInput方法检查是否有未读取的字节。如果有的话,跳过那些字节。

    解析连接

        parseConnection方法从套接字中获取到网络地址并把它赋予HttpRequestImpl对象。它也检查是否使用代理并把套接字赋予请求对象。parseConnection方法在Listing4.2中列出。
    Listing 4.2: The parseConnection method
    private void parseConnection(Socket socket) throws IOException, ServletException {
       if (debug >= 2)
           log(" parseConnection: address=" + socket.getInetAddress() +
               ", port=" + connector.getPort());
       ((HttpRequestImpl) request).setInet(socket.getInetAddress());
       if (proxyPort != 0)
           request.setServerPort(proxyPort);
       else
       request.setServerPort(serverPort);
       request.setSocket(socket);
    }

    解析请求

        parseRequest方法是第3章中类似方法的完整版本。如果你很好的理解第3章的话,你通过阅读这个方法应该可以理解这个方法是怎么运行的。

    解析头部

        默认链接器的parseHeaders方法使用包org.apache.catalina.connector.http里边的HttpHeader和DefaultHeaders类。类HttpHeader指代一个HTTP请求头部。类HttpHeader不是像第3章那样使用字符串,而是使用字符数据用来避免昂贵的字符串操作。类DefaultHeaders是一个final类,在字符数组中包含了标准的HTTP请求头部:
    standard HTTP request headers in character arrays:
    static final char[] AUTHORIZATION_NAME = "authorization".toCharArray();
    static final char[] ACCEPT_LANGUAGE_NAME = "accept-language".toCharArray();
    static final char[] COOKIE_NAME = "cookie".toCharArray();
    ...
        parseHeaders方法包含一个while循环,可以持续读取HTTP请求直到再也没有更多的头部可以读取到。while循环首先调用请求对象的allocateHeader方法来获取一个空的HttpHead实例。这个实例被传递给
    SocketInputStream的readHeader方法。
    HttpHeader header = request.allocateHeader();
    // Read the next header
    input.readHeader(header);
        假如所有的头部都被已经被读取的话,readHeader方法将不会赋值给HttpHeader实例,这个时候parseHeaders方法将会返回。
    if (header.nameEnd == 0) {
       if (header.valueEnd == 0) {
           return;
       }
       else {
           throw new        ServletException(sm.getString("httpProcessor.parseHeaders.colon"));
       }
    }
        如果存在一个头部的名称的话,这里必须同样会有一个头部的值:
    String value = new String(header.value, 0, header.valueEnd);
        接下去,像第3章那样,parseHeaders方法将会把头部名称和DefaultHeaders里边的名称做对比。注意的是,这样的对比是基于两个字符数组之间,而不是两个字符串之间的。
    if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {
       request.setAuthorization(value);
    }
    else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {
       parseAcceptLanguage(value);
    }
    else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
       // parse cookie
    }
    else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
       // get content length
    }
    else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
       request.setContentType(value);
    }
    else if (header.equals(DefaultHeaders.HOST_NAME)) {
       // get host name
    }
    else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {
       if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {
           keepAlive = false;
           response.setHeader("Connection", "close");
       }
    }
    else if (header.equals(DefaultHeaders.EXPECT_NAME)) {
       if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE))
           sendAck = true;
       else
           throw new ServletException(sm.getstring
               ("httpProcessor.parseHeaders.unknownExpectation"));
    }
    else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {
       //request.setTransferEncoding(header);
    }
    request.nextHeader();

    简单容器的应用程序

        本章的应用程序的主要目的是展示默认连接器是怎样工作的。它包括两个类:
       ex04.pyrmont.core.SimpleContainer和ex04 pyrmont.startup.Bootstrap。类
    SimpleContainer实现了org.apache.catalina.container接口,所以它可以和连接器关联。类Bootstrap是用来启动应用程序的,我们已经移除了第3章带的应用程序中的连接器模块,类ServletProcessor和
    StaticResourceProcessor,所以你不能请求一个静态页面。
        类SimpleContainer展示在Listing 4.3.
    Listing 4.3: The SimpleContainer class
    package ex04.pyrmont.core;
    import java.beans.PropertyChangeListener;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.net.URLStreamHandler;
    import java.io.File;
    import java.io.IOException;
    import javax.naming.directory.DirContext;
    import javax.servlet.Servlet;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.apache.catalina.Cluster;
    import org.apache.catalina.Container;
    import org.apache.catalina.ContainerListener;
    import org.apache.catalina.Loader;
    import org.apache.catalina.Logger;
    import org.apache.catalina.Manager;
    import org.apache.catalina.Mapper;
    import org.apache.catalina.Realm;
    import org.apache.catalina.Request;
    import org.apache.catalina.Response;
    public class SimpleContainer implements Container {
       public static final String WEB_ROOT =
           System.getProperty("user.dir") + File.separator + "webroot";
       public SimpleContainer() { }
       public String getInfo() {
           return null;
       }
       public Loader getLoader() {
           return null;
       }
       public void setLoader(Loader loader) { }
       public Logger getLogger() {
           return null;
       }
       public void setLogger(Logger logger) { }
       public Manager getManager() {
           return null;
       }
       public void setManager(Manager manager) { }
       public Cluster getCluster() {
           return null;
       }
       public void setCluster(Cluster cluster) { }
       public String getName() {
           return null;
       }
       public void setName(String name) { }
       public Container getParent() {
           return null;
       }
       public void setParent(Container container) { }
       public ClassLoader getParentClassLoader() {
           return null;
       }
       public void setParentClassLoader(ClassLoader parent) { }
       public Realm getRealm() {
           return null;
       }
       public void setRealm(Realm realm) { }
       public DirContext getResources() {
           return null;
       }
       public void setResources(DirContext resources) { }
        public void addChild(Container child) { }
       public void addContainerListener(ContainerListener listener) { }
       public void addMapper(Mapper mapper) { }
       public void addPropertyChangeListener(
    PropertyChangeListener listener) { }
    public Container findchild(String name) {
    return null;
    }
    public Container[] findChildren() {
    return null;
    }
    public ContainerListener[] findContainerListeners() {
    return null;
    }
    public Mapper findMapper(String protocol) {
    return null;
    }
    public Mapper[] findMappers() {
    return null;
    }
    public void invoke(Request request, Response response)
    throws IoException, ServletException {
    string servletName = ( (Httpservletrequest)
    request).getRequestURI();
    servletName = servletName.substring(servletName.lastIndexof("/") +
    1);
    URLClassLoader loader = null;
    try {
    URL[] urls = new URL[1];
    URLStreamHandler streamHandler = null;
    File classpath = new File(WEB_ROOT);
    string repository = (new URL("file",null,
    classpath.getCanonicalpath() + File.separator)).toString();
    urls[0] = new URL(null, repository, streamHandler);
    loader = new URLClassLoader(urls);
    }
    catch (IOException e) {
    System.out.println(e.toString() );
    }
    Class myClass = null;
    try {
    myClass = loader.loadclass(servletName);
    }
    catch (classNotFoundException e) {
    System.out.println(e.toString());
    }
    servlet servlet = null;
    try {
    servlet = (Servlet) myClass.newInstance();
    servlet.service((HttpServletRequest) request,
    (HttpServletResponse) response);
    }
    catch (Exception e) {
    System.out.println(e.toString());
    }
    catch (Throwable e) {
    System.out.println(e.toString());
    }
    }
    public Container map(Request request, boolean update) {
    return null;
    }
    public void removeChild(Container child) { }
    public void removeContainerListener(ContainerListener listener) { }
    public void removeMapper(Mapper mapper) { }
    public void removoPropertyChangeListener(
    PropertyChangeListener listener) {
    }
    }
        我只是提供了SimpleContainer类的invoke方法的实现,因为默认连接器将会调用这个方法。invoke方法创建了一个类加载器,加载servlet类,并调用它的service方法。这个方法和第3章的ServletProcessor类在哦个的process方法非常类似。
        Bootstrap类在Listing 4.4在列出.
       Listing 4.4: The ex04.pyrmont.startup.Bootstrap class
    package ex04.pyrmont.startup;
    import ex04.pyrmont.core.simplecontainer;
    import org.apache.catalina.connector.http.HttpConnector;
    public final class Bootstrap {
    public static void main(string[] args) {
    HttpConnector connector = new HttpConnector();
    SimpleContainer container = new SimpleContainer();
    connector.setContainer(container);
    try {
    connector.initialize();
    connector.start();
    // make the application wait until we press any key.
    System in.read();
    }
    catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
        Bootstrap 类的main方法构造了一个org.apache.catalina.connector.http.HttpConnector实例和一个 SimpleContainer实例。它接下去调用conncetor的setContainer方法传递container,让connector和container关联起来。下一步,它调用connector的initialize和start方法。这将会使得connector为处理8080端口上的任何请求做好了准备。
        你可以通过在控制台中输入一个按键来终止这个应用程序。

    运行应用程序

        要在Windows中运行这个程序的话,在工作目录下输入以下内容:

    java -classpath ./lib/servlet.jar;./ ex04.pyrmont.startup.Bootstrap
        在Linux的话,你可以使用分号来分隔两个库。
    java -classpath ./lib/servlet.jar:./ ex04.pyrmont.startup.Bootstrap
        你可以和第三章那样调用PrimitiveServlet和ModernServlet。
        注意的是你不能请求index.html,因为没有静态资源的处理器。

    总结

        本章展示了如何构建一个能和Catalina工作的Tomcat连接器。剖析了Tomcat4的默认连接器的代码并用这个连接器构建了一个小应用程序。接下来的章节的所有应用程序都会使用默认连接器。

  • 相关阅读:
    第一周学习进度
    四则运算
    添加课程
    继承和多态的动手动脑
    String 方法
    【CoreData】分页查询和模糊查询
    【CoreData】表之间的关联
    代码创建storyboard
    UIWindows 使用注意
    UIApplicationDelegate
  • 原文地址:https://www.cnblogs.com/macula7/p/1960779.html
Copyright © 2011-2022 走看看