深入剖析Tomcat(四) 剖析Tomcat的默认连接器

发布于:2024-04-25 ⋅ 阅读:(20) ⋅ 点赞:(0)

上篇文章已经实现了一个简易版的连接器,理解了那个连接器,再看Tomcat的默认连接器就会轻松很多,本章中的默认连接器指的是Tomcat4的默认连接器,尽管该连接器已经被弃用了,被另一个运行速度更快的连接器 Coyote 取代,但它仍然是一个很不错的学习工具。

Tomcat的连接器是一个独立的模块,可以被插入到servlet容器中。之所以说它独立,是因为市面上的连接器有很多,只要它满足了catalina中的连接器要求,那就可以放入Tomcat中使用而不用修改其他代码。类似于我们电脑中的内存条,只要主板支持,就有很多厂家的内存条供我们选择,如果空间不够用了还可以选择更大存储的内存条,是一个可插拔的设备。

那么catalina能使用的连接器,有哪些要求呢?

  1. 实现了org.apache.catalina.Connector接口
  2. 负责创建实现了org.apache.catalina.Request接口的request对象
  3. 负责创建实现了org.apache.catalina.Response接口的response对象

与我们的简易连接器相同,Tomcat默认连接器的功能也是接收socket请求,处理socket请求,建立request、response对象,调用容器的invoke方法这一套流程。

Tomcat的容器类应该是实现了org.apache.catalina.Container接口的类,该接口的关键方法为 void invoke(Request request, Response response) 方法,此方法应该实现http请求的分发功能,将请求交给指定servlet进行处理。

Tomcat默认连接器与我们上章中简易连接器的最大区别就是支持并发请求。如何支持并发的呢?默认连接器的做法是

1.先将HttpProcessor类继承Runnable接口,将处理http请求的过程放到一个独立线程中来执行

2.创建多个HttpProcessor对象,也就是创建了多个线程,连接器中维护一个HttpProcessor对象池,每收到一个Socket连接就从对象池中取一个HttpProcessor对象,也就是选一个HttpProcessor线程来处理。

这个做法很像我们常说的线程池,我们现在都通过ThreadPoolExecutor来创建线程池了,为什么默认连接器不用这个方式呢?因为那会儿的jdk还是1.4版本,还没有ThreadPoolExecutor,ThreadPoolExecutor是jdk 1.5版本才引入的。也许现在的连接器已经使用上线程池了,但是了解默认连接器的工作方式仍然能让你对多线程编程多点体会。

在正式介绍连接器的代码设计之前,先说一下HTTP 1.1的几个新特性

HTTP 1.1的几个新特性

1.持久连接(长连接)

HTTP 1.1之前,浏览器与服务器建立的连接都是短连接,一次请求结束后,连接即刻关闭。但是随着网页内容越来越丰富,一个网页的展现往往需要多次请求后台,如果每次请求建立的连接都是用完即关的话,会很浪费资源,效率也不高。所以HTTP 1.1引入了长连接的概念,反映在HTTP请求头中就是 Connection: keep-alive ,当请求头或响应头中带上了这个长链接标识的话,就代表客户端或服务器开启了长链接,接下来该网页的请求都会使用者一个Socket连接来进行通信,效率大大提高。

那什么时候长连接会被关闭呢?

  • 当一端的HTTP头部信息中带上了 Connection: close ,则代表该连接在本次请求后就需要被关闭了。
  • 在服务器端,通常会设置连接的空闲超时时间(idle timeout)。如果连接在一段时间内没有活动,即没有新的请求-响应交互,服务器会主动关闭连接
2.块编码

这块内容在第二章中已经介绍过,不再重复介绍。

3.状态码100的使用

使用HTTP1.1的客户端可以在向服务端发送请求体之前,先发送一个这个消息

Expect:100-continue

这个通常用在客户端准备了一个特大消息体的时候,先问候一下服务端看它在不在,如果服务端不在,那你客户端拖着一个特大消息体扔给互联网,那属实是浪费所有资源。

当客户端接收到 “ HTTP/1.1 100 Continue” 响应消息后,代表服务器是在岗的,就可以继续沟通了。

好,HTTP1.1的新特性已讲完,现在回到连接器的设计上。

Tomcat默认连接器的设计

来看下默认连接器的UML类图

HTTPConnector类实现了org.apache.catalina.Connector接口,代表它是一个连接器。连接器必须持有一个实现了org.apache.catalina.Container接口的Servlet容器类的引用,本章中这个Servlet容器类为 ex04.pyrmont.core.SimpleContainer 。

HTTPConnector负责启动一个独立线程接收Socket连接,而具体的处理逻辑交给HttpProcessor线程去完成,由于HTTPConnector持有一个HttpProcessor线程池,所以此连接器能支持并发连接。

HttpProcessor处理器负责解析http请求,最终封装成org.apache.catalina.Request与org.apache.catalina.Response两个对象并作为参数调用Servlet容器的 invoke 方法。

SimpleContainer的invoke方法逻辑与前一章的ServletProcessor#process 方法功能差不多,都是加载指定servlet类,并调用该servlet类的service方法。

接下来看看具体的代码设计

先声明一下,org.apache包的代码都是Tomcat4的源码,本章介绍的默认连接器就是在这个包下

由于本章的连接器使用的是Tomcat的默认连接器,所以属于本章的代码量就很少了,而且由于源码也没有bug,我就不再重新编码了

Bootstrap类

这仍然是启动整个Web容器的启动类。该类创建了连接器与Servlet容器,并将两者做了个关联。在执行完HttpConnector的 initialize() 与 start() 两个方法后,Web容器就启动了。

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) {
        // 创建连接器与Servlet容器,并做一个关联
        HttpConnector connector = new HttpConnector();
        SimpleContainer container = new SimpleContainer();
        connector.setContainer(container);
        try {
            connector.initialize();
            connector.start();

            // 接收键盘输入,在这里的目的是保活main线程
            System.in.read();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

HttpConnector类

HttpConnector实现了org.apache.catalina.Connector接口,代表它是一个连接器,接口需要实现的主要方法有 getContainer() 、setContainer()、createRequest()、createResponse() 。

setContainer用于关联一个servlet容器。createRequest负责创建一个servlet容器需要的request对象,createResponse负责创建一个servlet容器需要的response对象。

HttpConnector实现了Runnable接口,代表它也是一个线程对象,以独立线程运行,主要逻辑在run方法中。另外,该线程以守护线程的方式启动,在Tomcat进程中只要还存在一个其他非守护线程在运行,连接器线程就不会退出,但是如果非守护线程都停止了,那么连接器线程也没存在的必要了,我想Tomcat的设计者也是这样考虑的吧。

HttpConnector实现了org.apache.catalina.Lifecycle接口,Lifecycle接口负责维护Catalina中各组件的生命周期,这个到第六章再做具体介绍,这里你且知道HttpConnector实例创建后就需要马上执行它的两个生命周期方法initialize()、start() 。

initialize方法的作用是给连接器创建一个ServerSocket对象。

start方法的作用是启动连接器线程,创建并启动最小数量的 processor 线程。start方法执行完后连接器就可以开始工作了。

HttpConnector线程的run方法中有一个while循环,其实就是ServerSocket循环等待新的Socket连接进来,然后将Socket连接通过 processor.assign(socket) 方法交给HttpProcessor线程去异步执行处理逻辑。在介绍HttpProcessor类时我会详细介绍这个assign方法。

HttpConnector通过何种方式拿到一个空闲可用的HttpProcessor线程呢?答案就是HttpConnector内部维护了一个HttpProcessor对象池,private Stack processors = new Stack(); 存在于这个栈中的processor就是空闲可用的processor,使用的时候出栈即可,processor在完成一次任务后会将自己重新压入栈中。

由于HttpConnector源码代码量很大,我精简了一下,仅列出几个主要的属性及方法供你串通逻辑。

public final class HttpConnector implements Connector, Lifecycle, Runnable {

    // ServerSocket的backlog参数,最大等待连接数量
    private int acceptCount = 10;

    // 关联的容器
    protected Container container = null;

    // 已经被创建的 processor 集合
    private final Vector<HttpProcessor> created = new Vector<>();

    // 空闲可用的 processor 集合
    private Stack processors = new Stack();

    // 当前已经创建的processors数量
    private int curProcessors = 0;

    // 最小processors数量
    protected int minProcessors = 5;

    // 最大processors数量,如果为负数,代表无限制
    private int maxProcessors = 20;

    // 监听TCP连接的 ServerSocket
    private ServerSocket serverSocket = null;

    // 连接器监听的端口
    private int port = 8080;

    // 运行连接器的线程
    private Thread thread = null;

    // 线程同步对象,加锁用
    private Object threadSync = new Object();

    // --------------------------------------------------------- Lifecycle Methods

    /**
     * 初始化连接器 (这个方法就创建了一个 ServerSocket)
     */
    public void initialize() throws LifecycleException {
        if (initialized) {
            throw new LifecycleException(sm.getString("httpConnector.alreadyInitialized"));
        }

        this.initialized = true;
        Exception eRethrow = null;

        // Establish a server socket on the specified port
        try {
            serverSocket = open();
        } catch (Exception ex) {
            // 为了代码好看,这里将所有的异常合并了,源码中针对不同的异常打了不同的日志
            log("httpConnector, have problem: ", ex);
            eRethrow = ex;
        }

        if (eRethrow != null) {
            throw new LifecycleException(threadName + ".open", eRethrow);
        }
    }

    /**
     * 开启连接器线程,创建最小数量的 processor 线程,这些线程创建完后就可以接收http请求了
     */
    public void start() throws LifecycleException {

        // 检查是否启动过了
        if (started) {
            throw new LifecycleException(sm.getString("httpConnector.alreadyStarted"));
        }
        threadName = "HttpConnector[" + port + "]";
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;

        // 开启连接器线程
        threadStart();

        // 创建最小数量的 processor
        while (curProcessors < minProcessors) {
            if ((maxProcessors > 0) && (curProcessors >= maxProcessors)) {
                break;
            }
            HttpProcessor processor = newProcessor();
            // 将 processor 入栈
            recycle(processor);
        }

    }

    /**
     * 开启连接器线程,并且此线程为守护线程
     */
    private void threadStart() {
        thread = new Thread(this, threadName);
        thread.setDaemon(true);
        thread.start();
    }

    /**
     * 通过连接器终止所有processor线程  --本章暂时没用到,简单看下就行
     */
    public void stop() throws LifecycleException {

        // Validate and update our current state
        if (!started) {
            throw new LifecycleException(sm.getString("httpConnector.notStarted"));
        }
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        started = false;

        // Gracefully shut down all processors we have created
        for (int i = created.size() - 1; i >= 0; i--) {
            HttpProcessor processor = (HttpProcessor) created.elementAt(i);
            if (processor != null) {
                try {
                    ((Lifecycle) processor).stop();
                } catch (LifecycleException e) {
                    log("HttpConnector.stop", e);
                }
            }
        }

        synchronized (threadSync) {
            // Close the server socket we were using
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    ;
                }
            }
            // Stop our background thread
            threadStop();
        }
        serverSocket = null;
    }

    /**
     * 关闭连接器线程
     */
    private void threadStop() {
        log(sm.getString("httpConnector.stopping"));

        stopped = true;
        try {
            threadSync.wait(5000);
        } catch (InterruptedException e) {
            ;
        }
        thread = null;
    }

    // ---------------------------------------------- Background Thread Methods

    /**
     * 连接器的后台线程run方法,负责监听 TCP/IP 连接,并将连接交给合适的processor去处理
     */
    public void run() {
        // 循环执行,直到 收到停止命令(stopped变为true)
        while (!stopped) {
            Socket socket;
            try {
                socket = serverSocket.accept();
                // 设置 Socket 的超时时间。这个方法的作用是当 Socket 进行 I/O 操作时,
                // 如果在指定的时间内没有完成读写操作,就会抛出 java.net.SocketTimeoutException 异常。
                if (connectionTimeout > 0) {
                    socket.setSoTimeout(connectionTimeout);
                }
                // TCP_NODELAY为true时,代表关闭Nagle算法,大部分情况下能提高tcp通信效率
                socket.setTcpNoDelay(tcpNoDelay);
            } catch (AccessControlException ace) {
                log("socket accept security exception", ace);
                continue;
            } catch (IOException e) {
                try {
                    // 如果socket创建失败了,则重新创建一下ServerSocket,如果仍然失败的话,就退出连接器线程
                    synchronized (threadSync) {
                        if (started && !stopped) {
                            log("accept error: ", e);
                        }
                        if (!stopped) {
                            serverSocket.close();
                            serverSocket = open();
                        }
                    }
                } catch (Exception ex) {
                    // 其实这里的日志打印 本来根据不同异常分的很细,这里为了代码好看,我且将它们省略了
                    log("socket reopen, have problem: ", ex);
                    // 又发生了异常,退出while循环
                    break;
                }

                continue;
            }

            // 获取一个 processor 来接管本次 socket 通信
            HttpProcessor processor = createProcessor();
            // 如果没有 processor 可用的话,则放弃本次 socket 通信
            if (processor == null) {
                try {
                    log(sm.getString("httpConnector.noProcessor"));
                    socket.close();
                } catch (IOException e) {

                }
                continue;
            }
            // processor开始接管socket,processor处理完后会自动回收它自己(调用连接器的recycle方法),并准备处理下次http请求
            processor.assign(socket);
        }
        // while 循环到此结束

        // Notify the threadStop() method that we have shut ourselves down
        synchronized (threadSync) {
            threadSync.notifyAll();
        }

    }

    // -------------------------------------------------------- Private Methods

    /**
     * 创建或者分配一个可用的 processor,如果 processor数量已经达到最大值则返回null
     */
    private HttpProcessor createProcessor() {

        synchronized (processors) {
            if (processors.size() > 0) {
                return ((HttpProcessor) processors.pop());
            }
            if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
                return (newProcessor());
            } else {
                if (maxProcessors < 0) {
                    return (newProcessor());
                } else {
                    return (null);
                }
            }
        }

    }

    /**
     * 创建一个新的 processor 用以处理 http 请求,
     * 启动 processor 线程后,return 此 processor。
     */
    private HttpProcessor newProcessor() {
        HttpProcessor processor = new HttpProcessor(this, curProcessors++);
        try {
            // 启动 processor 线程
            processor.start();
        } catch (LifecycleException e) {
            log("newProcessor", e);
            return (null);
        }
        // 创建过的 processor 记录到一个集合中,连接器销毁时会用到(stop方法)
        created.addElement(processor);
        return processor;

    }

    // -------------------------------------------------------- Package Methods

    /**
     * 回收指定的 Processor, 以便于重新使用它
     * 回收方式:将processor入栈,下次http请求到来时就可以直接分配给它了
     */
    void recycle(HttpProcessor processor) {
        processors.push(processor);
    }

    // --------------------------------------------------------- Public Methods

    // 获取连接器关联的servlet容器
    public Container getContainer() {
        return (container);
    }

    // 将一个servlet容器与该连接器做关联
    public void setContainer(Container container) {
        this.container = container;
    }

    // 创建一个可以给Servlet容器使用的Request对象  --此方法给processor线程使用
    public Request createRequest() {
        HttpRequestImpl request = new HttpRequestImpl();
        request.setConnector(this);
        return (request);
    }

    // 创建一个可以给Servlet容器使用的Response对象  --此方法给processor线程使用
    public Response createResponse() {
        HttpResponseImpl response = new HttpResponseImpl();
        response.setConnector(this);
        return (response);
    }

}

HttpProcessor类

HttpProcessor类要干的事和第三章中的HttpProcessor一样,都是处理socket连接,将http请求解析为request与response对象后,交给容器处理。不同的是,本章的HttpProcessor实现了Runnable接口,也就是说本章的HttpProcessor是要起一个独立线程来干事的,这样的话,连接器就不用阻塞等待processor处理完一个请求后再去处理下一个了;连接器拿到socket连接后直接丢给processor线程异步处理,就可以实现并发连接了。

连接器线程与处理器线程通过两个方法与一个属性值来沟通。两个方法是 assign与await,一个属性是socket。

来对比看看这两个方法

下面通过一个时序图来展现下两个线程配合的过程

这就是一个正常的交互流程了,这个流程下其实只用到了下图中标号为1的 wait(),notifyAll()方法,那么标号为2的这一对是干啥用的呢?我理解的是:预防连接器在assign方法中执行完notifyAll又释放锁后,不知怎么着马上又执行到了同一个处理器的assign方法,并且先于该处理器线程获取到锁,这时候处理器线程还没有取走上一个socket请求呢,所以得等待处理器线程取走上一个socket。处理器线程取走socket后,notifyAll()通知连接器线程:你可以继续了,然后连接器线程结束等待,继续执行。

HttpProcessor中真正解析http请求的方法为process方法,也是解析请求行,请求头的一系列解析流程,最终封装成Request与Response两个对象交给Servlet容器去执行后续操作。

下面是我精简后的代码,仅保留了主要的几个方法

public final class HttpProcessor implements Lifecycle, Runnable {

    // ----------------------------------------------------------- Constructors
    public HttpProcessor(HttpConnector connector, int id) {
        super();
        this.connector = connector;
        this.debug = connector.getDebug();
        this.id = id;
        this.proxyName = connector.getProxyName();
        this.proxyPort = connector.getProxyPort();
        this.request = (HttpRequestImpl) connector.createRequest();
        this.response = (HttpResponseImpl) connector.createResponse();
        this.serverPort = connector.getPort();
        this.threadName = "HttpProcessor[" + connector.getPort() + "][" + id + "]";
    }


    // ----------------------------------------------------- Instance Variables

    // 是否有一个新的socket可用了
    private boolean available = false;

    // 关联的连接器对象
    private HttpConnector connector = null;

    // 需要给到Servlet容器的request对象
    private HttpRequestImpl request = null;

    // 需要给到Servlet容器的response对象
    private HttpResponseImpl response = null;


    // 连接器监听的端口
    private int serverPort = 0;

    // 当前处理器线程正在处理的socket,这个socket对象用于 连接器线程与处理器线程之间的通信
    private Socket socket = null;

    // 处理器线程
    private Thread thread = null;

    // 线程的同步对象,加锁用
    private Object threadSync = new Object();

    // Keep alive 标志
    private boolean keepAlive = false;

    // 是否是HTTP/1.1 客户端.
    private boolean http11 = true;


    // 如果客户端请求接收请求确认,则为True。如果是这样,服务器将在成功解析请求报头之后,在开始读取请求实体体之前发送一个初步的100 Continue响应。
    private boolean sendAck = false;


    // 回应客户端的 “Expect:100-continue” 请求
    private static final byte[] ack = (new String("HTTP/1.1 100 Continue\r\n\r\n")).getBytes();


    private HttpRequestLine requestLine = new HttpRequestLine();

    // Processor 当前状态
    private int status = Constants.PROCESSOR_IDLE;

    // ------------------------------------------------------ Lifecycle Methods

    /**
     * 开启processor线程,用以处理http请求
     */
    public void start() throws LifecycleException {
        if (started) {
            throw new LifecycleException(sm.getString("httpProcessor.alreadyStarted"));
        }
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;

        threadStart();
    }


    /**
     * 结束processor线程  --本章暂时没用到这个方法
     */
    public void stop() throws LifecycleException {
        if (!started) throw new LifecycleException(sm.getString("httpProcessor.notStarted"));
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        started = false;

        threadStop();
    }

    // ---------------------------------------------- Background Thread Methods


    /**
     * processor线程的run方法
     */
    public void run() {

        // processor线程会一直循环等待 处理请求,直到接收到一个停止信号
        while (!stopped) {

            // 阻塞等待下一个socket分配给当前processor
            Socket socket = await();
            if (socket == null) {
                continue;
            }

            // 处理这个 socket 请求
            try {
                process(socket);
            } catch (Throwable t) {
                log("process.invoke", t);
            }

            // 处理完这次请求了,让连接器回收自己,准备接收下次socket请求
            connector.recycle(this);
        }

        // Tell threadStop() we have shut ourselves down successfully
        synchronized (threadSync) {
            threadSync.notifyAll();
        }

    }


    /**
     * 启动processing线程
     */
    private void threadStart() {

        log(sm.getString("httpProcessor.starting"));

        thread = new Thread(this, threadName);
        thread.setDaemon(true);
        thread.start();

        if (debug >= 1) log(" Background thread has been started");

    }


    /**
     * 停止processing线程
     */
    private void threadStop() {
        log(sm.getString("httpProcessor.stopping"));

        stopped = true;
        assign(null);

        if (status != Constants.PROCESSOR_IDLE) {
            // Only wait if the processor is actually processing a command
            synchronized (threadSync) {
                try {
                    threadSync.wait(5000);
                } catch (InterruptedException e) {
                    ;
                }
            }
        }
        thread = null;
    }


    // -------------------------------------------------------- Package Methods


    /**
     * 这个方法是给连接器线程调用的。用于将socket交给HttpProcessor线程进行异步处理,以便连接器可以同时处理多个请求。
     *
     */
    synchronized void assign(Socket socket) {

        // 等待这个 Processor 获取上一个 Socket。
        // ps:如果这个方法执行完释放锁后,处理线程processor不给力,又被连接器线程给截胡了(理论上不存在),
        //    率先获取了processor对象锁,则连接器线程需要等待processor将上一个socket拿走后,才能给socket属性set新值
        while (available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        // 存储最新可用的 Socket,并 notify 当前这个processor对象对应的处理器线程
        this.socket = socket;
        available = true;
        notifyAll();

        if ((debug >= 1) && (socket != null)) {
            log(" An incoming request is being assigned");
        }

    }


    // -------------------------------------------------------- Private Methods


    /**
     * 等待连接器分配一个新的Socket过来。如何连接器正在关闭的话,连接器会分配一个为null的Socket过来
     */
    private synchronized Socket await() {

        // 等待连接器给分配一个新的 Socket
        while (!available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        // 告诉连接器,你分配来的Socket我收到了
        Socket socket = this.socket;
        available = false;
        notifyAll();

        if ((debug >= 1) && (socket != null)) {
            log("  The incoming request has been awaited");
        }

        return socket;
    }

    /**
     * 处理socket连接中携带的http请求
     */
    private void process(Socket socket) {
        boolean ok = true;
        boolean finishResponse = true;
        SocketInputStream input = null;
        OutputStream output = null;

        // 结构化并初始化我们需要的对象
        try {
            input = new SocketInputStream(socket.getInputStream(), connector.getBufferSize());
        } catch (Exception e) {
            log("process.create", e);
            ok = false;
        }

        keepAlive = true;
        // 循环接收处理此长链接上的请求,直到客户端发出关闭连接的请求
        while (!stopped && ok && keepAlive) {
            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);
                ok = false;
            }

            // 解析请求
            try {
                if (ok) {
                    // 解析并记录本次请求对应连接的参数(客户端的地址与连接器监听的端口)
                    parseConnection(socket);
                    // 解析请求行,得到queryString、method、protocol、uri信息
                    parseRequest(input, output);
                    if (!request.getRequest().getProtocol().startsWith("HTTP/0")) {
                        // 解析请求头,并将请求头中特殊的信息放到request对象的对应字段中
                        parseHeaders(input);
                    }
                    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);
                        }
                    }

                }
            } catch (EOFException e) {
                // 这很可能是客户端或服务器上的套接字断开连接了
                ok = false;
                finishResponse = false;
            } catch (ServletException e) {
                ok = false;
                try {
                    ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST);
                } catch (Exception f) {
                    ;
                }
            } catch (InterruptedIOException e) {
                if (debug > 1) {
                    try {
                        log("process.parse", e);
                        ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST);
                    } catch (Exception f) {
                        ;
                    }
                }
                ok = false;
            } catch (Exception e) {
                try {
                    log("process.parse", e);
                    ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST);
                } catch (Exception f) {
                    ;
                }
                ok = false;
            }

            // 将组装好的请求对象交给Servlet容器去进一步处理
            try {
                ((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate());
                if (ok) {
                    connector.getContainer().invoke(request, response);
                }
            } catch (ServletException e) {
                log("process.invoke", e);
                try {
                    ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                } catch (Exception f) {
                    ;
                }
                ok = false;
            } catch (InterruptedIOException e) {
                ok = false;
            } catch (Throwable e) {
                log("process.invoke", e);
                try {
                    ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                } catch (Exception f) {
                    ;
                }
                ok = false;
            }

            // Finish up the handling of the request
            if (finishResponse) {
                try {
                    response.finishResponse();
                } catch (IOException e) {
                    ok = false;
                } catch (Throwable e) {
                    log("process.invoke", e);
                    ok = false;
                }
                try {
                    request.finishRequest();
                } catch (IOException e) {
                    ok = false;
                } catch (Throwable e) {
                    log("process.invoke", e);
                    ok = false;
                }
                try {
                    if (output != null) output.flush();
                } catch (IOException e) {
                    ok = false;
                }
            }

            // 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();

        }
        // where循环结束

        try {
            shutdownInput(input);
            socket.close();
        } catch (IOException e) {
            ;
        } catch (Throwable e) {
            log("process.invoke", e);
        }
        socket = null;
    }


    /**
     * 解析并记录本次请求对应连接的参数(客户端的地址与连接器监听的端口)
     */
    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);

    }


    /**
     * 解析请求头,将解析出来的信息填充到request对象中
     */
    private void parseHeaders(SocketInputStream input) throws IOException, ServletException {

        while (true) {

            HttpHeader header = request.allocateHeader();

            // Read the next header
            input.readHeader(header);
            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);
            if (debug >= 1) {
                log(" Header " + new String(header.name, 0, header.nameEnd) + " = " + value);
            }

            // Set the corresponding request headers
            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)) {
                Cookie cookies[] = RequestUtil.parseCookieHeader(value);
                for (int i = 0; i < cookies.length; i++) {
                    if (cookies[i].getName().equals(Globals.SESSION_COOKIE_NAME)) {
                        // 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);
                            if (debug >= 1)
                                log(" Requested cookie session id is " + ((HttpServletRequest) request.getRequest()).getRequestedSessionId());
                        }
                    }
                    if (debug >= 1) log(" Adding cookie " + cookies[i].getName() + "=" + cookies[i].getValue());
                    request.addCookie(cookies[i]);
                }
            } else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
                int n = -1;
                try {
                    n = Integer.parseInt(value);
                } catch (Exception e) {
                    throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
                }
                request.setContentLength(n);
            } else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
                request.setContentType(value);
            } else if (header.equals(DefaultHeaders.HOST_NAME)) {
                int n = value.indexOf(':');
                if (n < 0) {
                    if (connector.getScheme().equals("http")) {
                        request.setServerPort(80);
                    } else if (connector.getScheme().equals("https")) {
                        request.setServerPort(443);
                    }
                    if (proxyName != null) request.setServerName(proxyName);
                    else request.setServerName(value);
                } else {
                    if (proxyName != null) request.setServerName(proxyName);
                    else request.setServerName(value.substring(0, n).trim());
                    if (proxyPort != 0) request.setServerPort(proxyPort);
                    else {
                        int port = 80;
                        try {
                            port = Integer.parseInt(value.substring(n + 1).trim());
                        } catch (Exception e) {
                            throw new ServletException(sm.getString("httpProcessor.parseHeaders.portNumber"));
                        }
                        request.setServerPort(port);
                    }
                }
            } else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {
                if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {
                    keepAlive = false;
                    response.setHeader("Connection", "close");
                }
                //request.setConnection(header);
                /*
                  if ("keep-alive".equalsIgnoreCase(value)) {
                  keepAlive = true;
                  }
                */
            } 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();

        }

    }


    /**
     * 解析请求行,得到 queryString、method、protocol、uri信息。如果uri中包含jsessionid的话,同时也把jsessionid解析出来
     * 如果是HTTP/1.1 协议的话,默认采用持久化连接,更早版本的话默认采用非持久化连接。
     * 无论什么版本HTTP协议,如果请求头中声明了Connection的值的话,则以这个值为准
     */
    private void parseRequest(SocketInputStream input, OutputStream output) throws IOException, ServletException {

        // 解析请求行
        input.readRequestLine(requestLine);

        // When the previous method returns, we're actually processing a request
        status = Constants.PROCESSOR_ACTIVE;

        String method = new String(requestLine.method, 0, requestLine.methodEnd);
        String uri;
        String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);

        if (protocol.length() == 0) {
            protocol = "HTTP/0.9";
        }

        // Now check if the connection should be kept alive after parsing the request.
        if (protocol.equals("HTTP/1.1")) {
            http11 = true;
            sendAck = false;
        } else {
            http11 = false;
            sendAck = false;
            // For HTTP/1.0, connection are not persistent by default,
            // unless specified with a Connection: Keep-Alive header.
            keepAlive = false;
        }

        // Validate the incoming request line
        if (method.length() < 1) {
            throw new ServletException(sm.getString("httpProcessor.parseRequest.method"));
        } else if (requestLine.uriEnd < 1) {
            throw new ServletException(sm.getString("httpProcessor.parseRequest.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));
            if (debug >= 1) log(" Query string is " + ((HttpServletRequest) request.getRequest()).getQueryString());
            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
        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;
            if (debug >= 1)
                log(" Requested URL session id is " + ((HttpServletRequest) request.getRequest()).getRequestedSessionId());
        } else {
            request.setRequestedSessionId(null);
            request.setRequestedSessionURL(false);
        }

        // 标准化 URI,对非正常的URI进行修正
        String normalizedUri = normalize(uri);
        if (debug >= 1) {
            log("Normalized: '" + uri + "' to '" + normalizedUri + "'");
        }

        // Set the corresponding request properties
        ((HttpRequest) request).setMethod(method);
        request.setProtocol(protocol);
        if (normalizedUri != null) {
            ((HttpRequest) request).setRequestURI(normalizedUri);
        } else {
            ((HttpRequest) request).setRequestURI(uri);
        }
        request.setSecure(connector.getSecure());
        request.setScheme(connector.getScheme());

        if (normalizedUri == null) {
            log(" Invalid request URI: '" + uri + "'");
            throw new ServletException("Invalid URI: " + uri + "'");
        }

        if (debug >= 1) {
            log(" Request is '" + method + "' for '" + uri + "' with protocol '" + protocol + "'");
        }

    }


    /**
     * Send a confirmation that a request has been processed when pipelining.
     * HTTP/1.1 100 Continue is sent back to the client.
     *
     * @param output Socket output stream
     */
    private void ackRequest(OutputStream output) throws IOException {
        if (sendAck) output.write(ack);
    }

    protected void shutdownInput(InputStream input) {
        try {
            int available = input.available();
            // skip any unread (bogus) bytes
            if (available > 0) {
                input.skip(available);
            }
        } catch (Throwable e) {
            ;
        }
    }

}

SimpleContainer类

这个类就不是Tomcat源码中的类了,这是我们自定义的一个Servlet容器类,它实现了org.apache.catalina.Container方法但是这里我们仅对invoke方法做了具体实现,因为默认连接器会调用该方法。该方法与第三章中ServletProcessor#process方法类似,创建类加载器,加载相关servlet类,创建servlet对象并调用其service方法。

下面是精简后的代码,仅保留了invoke方法

public class SimpleContainer implements Container {

    public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";

    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());
        }


    }

}

你可能发现了,这里怎么没用门面类呢?额,我只能说书中源码中也没有,我也懒得加了😂,下一章详细介绍Servlet容器时再说。

结果展示

启动Bootstrap类,浏览器访问结果如下

PrimitiveServlet

ModernServlet

注意,本章的简易Servlet容器仍然不能返回静态资源。

好,Tomcat的默认连接器到此就介绍完了,后面章节中会一直使用这个连接器。下一章将具体研究下Servlet容器,敬请期待!

源码分享

https://gitee.com/huo-ming-lu/HowTomcatWorks

本章的代码在这两个包下