zoukankan      html  css  js  c++  java
  • Tomcat 第四篇:请求处理流程(上)

    1. 引言

    既然是在讲 Tomcat ,那么一个 HTTP 请求的请求流程是无论如何也绕不开的。

    首先抛开所有,使用我们现有的知识面,猜测一下一个请求被 Tomcat 处理的过程:

    1. 客户端(浏览器)发送一个请求(HTTP)
    2. 建立 Socket 连接
    3. 通过 Socket 读取数据
    4. 根据协议(HTTP)解析请求
    5. 调用对应的代码完成响应
    

    上面这套流程,我相信任何一个 Java 码农都能想得到,当 Tomcat 接受到请求后,经过一系列的基础处理,最终会调用到我们自己的业务程序上,或者说是 Servlet 上,在早期,这些请求会由我们自己实现的 jsp 或者是 Servlet 进行接收,随着时代的发展以及演进,出现了 Struts 和 Spring 等中间件来帮助我们完成基础的请求处理,使得开发人员更加关注具体的业务。

    我想很多人都很好奇, Tomcat 是如何将这些 HTTP 请求转交给我们的 Servlet 的?

    2. Connector 初始化

    上一篇我们在聊 Tomcat 启动流程的时候,最后执行初始化的是 org.apache.catalina.connector.Connector#initInternal() ,这时整个初始化流程到了 Connector ,看一下这段代码:

    // 去除部分代码
    protected void initInternal() throws LifecycleException {
        super.initInternal();
        // Initialize adapter
        adapter = new CoyoteAdapter(this);
        protocolHandler.setAdapter(adapter);
        // ......
        try {
            protocolHandler.init();
        } catch (Exception e) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
        }
    }
    

    这段代码中主要做了两件事情:

    • 构造了 CoyoteAdapter 对象,并且将其设置为 ProtocolHandler 的 Adapter 。
    • 调用了 org.apache.coyote.ProtocolHandler#init() 的方法。

    先说第二件事情,调用了 org.apache.coyote.ProtocolHandler#init()ProtocolHandler 是在构造方法中进行的初始化,这里的核心代码是:

    setProtocol(protocol)
    

    再看下 setProtocol() 这个方法做了啥:

    public void setProtocol(String protocol) {
        boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
                AprLifecycleListener.getUseAprConnector();
        if ("HTTP/1.1".equals(protocol) || protocol == null) {
            if (aprConnector) {
                setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
            } else {
                setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
            }
        } else if ("AJP/1.3".equals(protocol)) {
            if (aprConnector) {
                setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
            } else {
                setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
            }
        } else {
            setProtocolHandlerClassName(protocol);
        }
    }
    

    看到这里可以知道,主逻辑分成了两块,一块是使用 HTTP/1.1 协议,另一块是使用了 AJP/1.3 的协议,这里通过协议的不同,最终初始化了不同的类。

    如果是使用 HTTP/1.1 的协议,则采用了 org.apache.coyote.http11.Http11AprProtocol 或者 org.apache.coyote.http11.Http11NioProtocol,如果是采用 AJP/1.3 则采用 org.apache.coyote.ajp.AjpAprProtocol 或者是 org.apache.coyote.ajp.AjpNioProtocol

    看下 org.apache.coyote.http11.Http11AprProtocolorg.apache.coyote.ajp.AjpAprProtocol 继承关系图:

    可以看到这两个类都继承自 org.apache.coyote.AbstractProtocol ,通过查看 org.apache.coyote.AbstractProtocol#init() 方法,可以看到是调用了 org.apache.tomcat.util.net.AbstractEndpoint#init() ,而 AbstractEndpoint 的实例化操作是在实例化 AjpProtocolHttp11Protocol 的时候在其构造函数中实例化的,而在 AjpProtocolHttp11Protocol 的构造函数中,实际上是都初始化了 org.apache.tomcat.util.net.JIoEndpoint ,只不过根据不同的 HTTP 或者是 AJP 协议,它们具有不同的连接处理类。其中 Http11Protocol 的连接处理类为 org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler ,而连接处理类为 org.apache.coyote.ajp.AjpProtocol.AjpConnectionHandler

    除此之外, ProtocolHandler 还有其他实现,都在 org.apache.coyote 这个包中,一些常见的类图如下:

    因此到这里我们基本清楚了 Connector 的初始化流程,总结如下:

    //1 HTTP/1.1协议连接器
    org.apache.catalina.connector.Connector#init
    ->org.apache.coyote.http11.Http11AprProtocol#init
    -->org.apache.tomcat.util.net.AprEndpoint#init
    (org.apache.coyote.http11.Http11AprProtocol.Http11ConnectionHandler)
    
    // 2 AJP/1.3协议连接器
    org.apache.catalina.connector.Connector#init
    ->org.apache.coyote.ajp.AjpAprProtocol#init
    -->org.apache.tomcat.util.net.AprEndpoint#init
    (org.apache.coyote.ajp.AjpAprProtocol.AjpConnectionHandler)
    

    3. Connector 启动

    ProtocolHandler 的初始化稍微有些特殊,Server、Service、Connector 这三个容器的初始化顺序为: Server -> Service -> Connector 。值得注意的是, ProtocolHandler 作为 Connector 的子容器,其初始化过程并不是由 Connector 的 initInternal 方法调用的,而是与启动过程一道被 Connector 的 startInternal 方法所调用。

    @Override
    protected void startInternal() throws LifecycleException {
        // Validate settings before starting
        if (getPort() < 0) {
            throw new LifecycleException(sm.getString(
                    "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
        }
        setState(LifecycleState.STARTING);
        try {
            protocolHandler.start();
        } catch (Exception e) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
        }
    }
    

    这里也总共做了两件事儿:

    • 将 Connector 容器的状态更改为启动中(LifecycleState.STARTING) 。
    • 启动 ProtocolHandler 。

    简单起见,以 Http11Protocol 为例介绍 ProtocolHandler 的 start 方法:

    由于 ProtocolHandler 是一个接口,它的 start 方法有两个抽象类进行实现:

    • org.apache.coyote.AbstractAjpProtocol
    • org.apache.coyote.ajp.AbstractProtocol

    这里我们仅讨论 org.apache.coyote.ajp.AbstractProtocol ,看下它的 start 的方法:

    @Override
    public void start() throws Exception {
        if (getLog().isInfoEnabled()) {
            getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
        }
    
        endpoint.start();
    
        // Start timeout thread
        asyncTimeout = new AsyncTimeout();
        Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
        int priority = endpoint.getThreadPriority();
        if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
            priority = Thread.NORM_PRIORITY;
        }
        timeoutThread.setPriority(priority);
        timeoutThread.setDaemon(true);
        timeoutThread.start();
    }
    

    这里最核心的一句代码是调用了 endpoint.start() ,这里的 endpoint 是抽象类 AbstractEndpoint#start() :

    public final void start() throws Exception {
        if (bindState == BindState.UNBOUND) {
            bind();
            bindState = BindState.BOUND_ON_START;
        }
        startInternal();
    }
    

    这一段也做了两件事儿:

    • 判断当前绑定状态,如果没有绑定,则会先去绑定,调用 bind()
    • 然后调用 startInternal() 进行初始化。

    AbstractEndpoint 有三个子类:

    我们专注于 AprEndpoint 这个子类,上面 AbstractEndpoint 抽象类中的两个方法 bind()startInternal() 都会在这个类中进行实现。

    我们先看 bind() 方法:

    /**
        * Initialize the endpoint.
        */
    @Override
    public void bind() throws Exception {
    
        // Create the root APR memory pool
        try {
            rootPool = Pool.create(0);
        } catch (UnsatisfiedLinkError e) {
            throw new Exception(sm.getString("endpoint.init.notavail"));
        }
    
        // Create the pool for the server socket
        serverSockPool = Pool.create(rootPool);
        // Create the APR address that will be bound
        String addressStr = null;
        if (getAddress() != null) {
            addressStr = getAddress().getHostAddress();
        }
        int family = Socket.APR_INET;
        if (Library.APR_HAVE_IPV6) {
            if (addressStr == null) {
                if (!OS.IS_BSD) {
                    family = Socket.APR_UNSPEC;
                }
            } else if (addressStr.indexOf(':') >= 0) {
                family = Socket.APR_UNSPEC;
            }
            }
    
        long inetAddress = Address.info(addressStr, family,
                getPort(), 0, rootPool);
        // Create the APR server socket
        serverSock = Socket.create(Address.getInfo(inetAddress).family,
                Socket.SOCK_STREAM,
                Socket.APR_PROTO_TCP, rootPool);
        if (OS.IS_UNIX) {
            Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
        }
        if (Library.APR_HAVE_IPV6) {
            if (getIpv6v6only()) {
                Socket.optSet(serverSock, Socket.APR_IPV6_V6ONLY, 1);
            } else {
                Socket.optSet(serverSock, Socket.APR_IPV6_V6ONLY, 0);
            }
        }
        // Deal with the firewalls that tend to drop the inactive sockets
        Socket.optSet(serverSock, Socket.APR_SO_KEEPALIVE, 1);
        // Bind the server socket
        int ret = Socket.bind(serverSock, inetAddress);
        if (ret != 0) {
            throw new Exception(sm.getString("endpoint.init.bind", "" + ret, Error.strerror(ret)));
        }
        // Start listening on the server socket
        ret = Socket.listen(serverSock, getAcceptCount());
        if (ret != 0) {
            throw new Exception(sm.getString("endpoint.init.listen", "" + ret, Error.strerror(ret)));
        }
        if (OS.IS_WIN32 || OS.IS_WIN64) {
            // On Windows set the reuseaddr flag after the bind/listen
            Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
        }
    
        // Enable Sendfile by default if it has not been configured but usage on
        // systems which don't support it cause major problems
        if (!useSendFileSet) {
            setUseSendfileInternal(Library.APR_HAS_SENDFILE);
        } else if (getUseSendfile() && !Library.APR_HAS_SENDFILE) {
            setUseSendfileInternal(false);
        }
    
        // Initialize thread count default for acceptor
        if (acceptorThreadCount == 0) {
            // FIXME: Doesn't seem to work that well with multiple accept threads
            acceptorThreadCount = 1;
        }
    
        // Delay accepting of new connections until data is available
        // Only Linux kernels 2.4 + have that implemented
        // on other platforms this call is noop and will return APR_ENOTIMPL.
        if (deferAccept) {
            if (Socket.optSet(serverSock, Socket.APR_TCP_DEFER_ACCEPT, 1) == Status.APR_ENOTIMPL) {
                deferAccept = false;
            }
        }
    
        // Initialize SSL if needed
        if (isSSLEnabled()) {
            for (SSLHostConfig sslHostConfig : sslHostConfigs.values()) {
                createSSLContext(sslHostConfig);
            }
            SSLHostConfig defaultSSLHostConfig = sslHostConfigs.get(getDefaultSSLHostConfigName());
            if (defaultSSLHostConfig == null) {
                throw new IllegalArgumentException(sm.getString("endpoint.noSslHostConfig",
                        getDefaultSSLHostConfigName(), getName()));
            }
            Long defaultSSLContext = defaultSSLHostConfig.getOpenSslContext();
            sslContext = defaultSSLContext.longValue();
            SSLContext.registerDefault(defaultSSLContext, this);
    
            // For now, sendfile is not supported with SSL
            if (getUseSendfile()) {
                setUseSendfileInternal(false);
                if (useSendFileSet) {
                    log.warn(sm.getString("endpoint.apr.noSendfileWithSSL"));
                }
            }
        }
    }
    

    这个方法上面的注释已经写的比较清楚了,首先第一个方法注释就告诉我们这个方法是用来初始化 endpoint 的,大体做了这么几件事儿:

    • 创建了 APR 的 rootPool ,从命名上看这应该是一个根连接池。
    • 创建一个 serverSockPool ,使用刚才创建的 rootPool 进行创建,这个命名大家就都看得懂了。
    • 创建用来做绑定的 APR 的地址。
    • 创建一个 APR server socket -> serverSock ,这里开启了 socket 。
    • 将刚才创建的 server 和 socket 进行绑定。
    • 开启 server socket 上面的监听。
    • 一些系统层面的设置。
    • 如果需要的话,还会进行一些 SSL 的相关设置。

    接着看下 startInternal() 方法:

    /**
      * Start the APR endpoint, creating acceptor, poller and sendfile threads.
      */
    @Override
    public void startInternal() throws Exception {
    
        if (!running) {
            running = true;
            paused = false;
    
            processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                    socketProperties.getProcessorCache());
    
            // Create worker collection
            if (getExecutor() == null) {
                createExecutor();
            }
    
            initializeConnectionLatch();
    
            // Start poller thread
            poller = new Poller();
            poller.init();
            Thread pollerThread = new Thread(poller, getName() + "-Poller");
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
    
            // Start sendfile thread
            if (getUseSendfile()) {
                sendfile = new Sendfile();
                sendfile.init();
                Thread sendfileThread =
                        new Thread(sendfile, getName() + "-Sendfile");
                sendfileThread.setPriority(threadPriority);
                sendfileThread.setDaemon(true);
                sendfileThread.start();
            }
    
            startAcceptorThreads();
        }
    }
    

    这个方法所有的前提条件都在于如果 AprEndpoint 尚未出于运行中,即 running == true ,首先如果没有创建线程池 getExecutor() == null ,则需要调用 createExecutor() 方法创建线程池和任务队列 TaskQueue

    public void createExecutor() {
        internalExecutor = true;
        TaskQueue taskqueue = new TaskQueue();
        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
        taskqueue.setParent( (ThreadPoolExecutor) executor);
    }
    

    剩下两个是创建了两个线程,分别是 poller 和 sendfile ,这两个都是 AprEndpoint 的内部类,这两个线程一个是用来做轮询,另一个是用来做数据发送。

    至此, Tomcat 中为请求处理的准备工作已经完成。

  • 相关阅读:
    mysql 性能监控
    拼接字符
    mysql 存储 2
    mysql 存储过程
    flock
    readfile() file_get_content f
    url_encode和base64
    jsdetox反混淆js内容,解密前端加密参数
    前端加密之使用firefox来解密
    v to ray做渗透测试
  • 原文地址:https://www.cnblogs.com/babycomeon/p/13737680.html
Copyright © 2011-2022 走看看