Tomcat 9 源码解析 -- 请求如何经过tomcat到达servlet

目录

请求如何到达Tomcat

在进入容器 后如何执行的

类中的执行流程

四个主要容器的执行流程


前面几篇文章讲解了Tomcat如何启动的,如何通过NIO,开启一个ServerSocket服务监听。

最后用户通过浏览器就可以直接访问到想要的资源。

 

下图中Connector部分:

一个Connector将在某个指定端口上侦听客户请求,并将获得的请求交给Engine来处理,从Engine处获得回应并返回客户。 Tomcat有两个典型的Connector: - 一个直接侦听来自browser的http请求 - 一个侦听来自其它WebServer的请求 Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求。 Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。

请求到达tomcat 的执行流程

 

 

请求如何到达Tomcat:

1.我们发起一个浏览器的请求。

2.请求会被之前tomcat启动后的ServerSocket监听到(这是通过TCP/UDP传输协议实现),

  Connector将请求交给Engin ---> Host --->Context 一层层过滤最后到达具体的Servlet。

3.Servlet处理完业务最后将请求返回给Connector,返回给用户。

在进入容器 后如何执行的

将上图进行展开来看一下 :

     Tomcat中的六个容器:

容器作用
Server容器一个StandardServer类实例就表示一个Server容器,server是tomcat的顶级构成容器
Service容器一个StandardService类实例就表示一个Service容器,Tomcat的次顶级容器,Service是这样一个集合:它由一个或者多个Connector组成,以及一个Engine,负责处理所有Connector所获得的客户请求。
Engine容器一个StandardEngine类实例就表示一个Engine容器。Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名。当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理,Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理
Host容器一个StandardHost类实例就表示一个Host容器,代表一个VirtualHost,虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配。每个虚拟主机下都可以部署(deploy)一个或者多个WebApp,每个Web App对应于一个Context,有一个Context path。当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。匹配的方法是“最长匹配”,所以一个path==”“的Context将成为该Host的默认Context。所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配
Context容器一个StandardContext类实例就表示一个Context容器。一个Context对应于一个Web Application,一个WebApplication由一个或者多个Servlet组成。Context在创建的时候将根据配置文件CATALINA_HOME/conf/web.xml和WEBAPP_HOME/WEB-INF/web.xml载入Servlet类。当Context获得请求时,将在自己的映射表(mappingtable)中寻找相匹配的Servlet类。如果找到,则执行该类,获得请求的回应,并返回
Wrapper容器一个StandardWrapper类实例就表示一个Wrapper容器,Wrapper容器负责管理一个Servlet,包括Servlet的装载、初始化、资源回收。Wrapper是最底层的容器,其不能在添加子容器了。Wrapper是一个接口,其标准实现类是StandardWrapper

 

Container用于封装和管理Servlet,以及具体处理Request请求,在Connector内部包含了4个子容器,结构图如下     

四个子容器的作用分别是: 

  • (1)Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine;
  • (2)Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点;
  • (3)Context:代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件;
  • (4)Wrapper:每一Wrapper封装着一个Servlet。

 

类中的执行流程:

 

四个主要容器的执行流程:

Container处理请求是使用Pipeline-Valve管道来处理的!(Valve是阀门之意)

Pipeline-Valve是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将处理后的请求返回,再让下一个处理着继续处理。

 

代码从 NioEndpoint开始 

1. 前面的文章讲过如何启动了一个NIO的server

 U socket = null;
                try {
                    // Accept the next incoming connection from the server
                    // socket
                    socket = endpoint.serverSocketAccept();

.............

 

2. 启动了server后,监听来自浏览器请求

当请求到达tomcat后  ,做为Socket接收者Acceptor会接受所有的请求,最为Socket轮询者Poller,Poller 会负责轮询上述产生的事件,将就绪的事件生成  SokcetProcessor  ,交给Excutor去执行。.

Iterator<SelectionKey> iterator =
                    keyCount > 0 ? selector.selectedKeys().iterator() : null;
                // Walk through the collection of ready keys and dispatch
                // any active event.
                 // 遍历SelectionKey,并调度活动事件,这个过程跟普通的nio类似
                while (iterator != null && iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    // 获取NioSocketWrapper,如果获取为空,说明其他线程调用了cancelKey()
                    // 则去除这个Key,否则调用processKey()
                    NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
                    // Attachment may be null if another thread has called
                    // cancelledKey()
                    if (attachment == null) {
                        iterator.remove();
                    } else {
                        iterator.remove();
                        LogPropertiesTest.debug("  --------------  18、Poller :  执行  processKey(sk, attachment);  方法, 执行类 :"+this.getClass());
                        processKey(sk, attachment); // 处理SelectKey  重要方法  !!!!!!!
                    } 
                }//while

我们可以看到这一行代码

processKey(sk, attachment); // 处理SelectKey  重要方法  !!!!!!!
此处遍历SelectionKey,并调度活动事件,这个过程跟普通的nio类似,开始处理一个SelectionKey

processKey()这个方法主要通过调用processSocket()方法创建一个SocketProcessor,然后丢到Tomcat线程池中去执行。每个Endpoint都有自己的SocketProcessor实现,
 从Endpoint的属性中可以看到,这个Processor也有缓存机制。
总结一下Poller所做的事:遍历PollerEvents队列,将每个事件中的通道感兴趣的事件注册到Selector,当事件就绪时,创建一个SocketProcessor或者从缓存中取出一个SocketProcessor,
然后放到线程池执行或者直接执行它的run方法执行。

protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
            try {
                if ( close ) {
                    cancelledKey(sk);
                } else if ( sk.isValid() && attachment != null ) {
                    if (sk.isReadable() || sk.isWritable() ) {
                        if ( attachment.getSendfileData() != null ) {
                            processSendfile(sk,attachment, false);
                        } else {
                            unreg(sk, attachment, sk.readyOps());
                            boolean closeSocket = false;
                            
                            // Read goes before write
                            if (sk.isReadable()) {
                            	//此处判断是否可读
                                if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
                                    closeSocket = true;
                                }
                            }
                            if (!closeSocket && sk.isWritable()) {
                                if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
                                    closeSocket = true;
                                }
                            }
                            if (closeSocket) {
                                cancelledKey(sk);
                            }
                        }
                    }
                } else {
                    //invalid key
                    cancelledKey(sk);
                }
            } catch ( CancelledKeyException ckx ) {
                cancelledKey(sk);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error("",t);
            }
        }

看到此行:

if (!processSocket(attachment, SocketEvent.OPEN_READ, true))

调用了父类AbstractEndpoint方法:

public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase<S> sc = processorCache.pop();
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
                executor.execute(sc);
            } else {
            	
                sc.run();
            }
        } catch (RejectedExecutionException ree) {
            getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
            return false;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This means we got an OOM or similar creating a thread, or that
            // the pool and its queue are full
            getLog().error(sm.getString("endpoint.process.fail"), t);
            return false;
        }
        return true;
    }

 

最终进入 SocketProcessor 的doRun()方法:

代码中已经给了注解解释。

@Override
        protected void doRun() {
            NioChannel socket = socketWrapper.getSocket();
            SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());

            try {
            	
            	// 这里的 handshake 是用来标记https的握手完成情况,
                // 如果是http不需要该握手阶段,在从   11111111111   处直接置为0
                int handshake = -1;

                try {
                    if (key != null) {
                        if (socket.isHandshakeComplete()) { // 1111111111 
                            // No TLS handshaking required. Let the handler
                            // process this socket / event combination.
                            handshake = 0;
                        } else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
                                event == SocketEvent.ERROR) {
                            // Unable to complete the TLS handshake. Treat it as
                            // if the handshake failed.
                        	 // 如果不能完成TLS握手过程,标记握手失败
                            handshake = -1;
                        } else {
                        	 // 处理https的SecureNioChannel覆写了该hanshake()方法
                            // 返回注册的SelectionKey
                            handshake = socket.handshake(key.isReadable(), key.isWritable());
                            // The handshake process reads/writes from/to the
                            // socket. status may therefore be OPEN_WRITE once
                            // the handshake completes. However, the handshake
                            // happens when the socket is opened so the status
                            // must always be OPEN_READ after it completes. It
                            // is OK to always set this as it is only used if
                            // the handshake completes.
                            event = SocketEvent.OPEN_READ;
                        }
                    }
                } catch (IOException x) {
                    handshake = -1;
                    if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x);
                } catch (CancelledKeyException ckx) {
                    handshake = -1;
                }
                if (handshake == 0) {  // 握手完成
                    SocketState state = SocketState.OPEN;  // 标记Socket状态
                    // Process the request from this socket
                    // 处理该Sockt里的请求
                    if (event == null) {
                        state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                    } else {
                        state = getHandler().process(socketWrapper, event);
                    }
                    if (state == SocketState.CLOSED) {
                        close(socket, key);  // 否则关闭通道
                    }
                } else if (handshake == -1 ) {  // 握手失败则关闭通道
                    close(socket, key);
                } else if (handshake == SelectionKey.OP_READ){  // TLS会走到这里
                    socketWrapper.registerReadInterest();   // 注册读就绪
                } else if (handshake == SelectionKey.OP_WRITE){
                    socketWrapper.registerWriteInterest(); // 注册写就绪
                } 
            } catch (CancelledKeyException cx) {
                socket.getPoller().cancelledKey(key);
            } catch (VirtualMachineError vme) {
                ExceptionUtils.handleThrowable(vme);
            } catch (Throwable t) {
                log.error("", t);
                socket.getPoller().cancelledKey(key);
            } finally {
                socketWrapper = null;
                event = null;
                //return to cache
                if (running && !paused) {
                    processorCache.push(this);  // 将SocketProcessor放回缓存中
                }
            }
        }
    }

简而言之  重要的代码还是看这几行:

                   // 处理该Sockt里的请求
                    if (event == null) {
                        state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                    } else {
                        state = getHandler().process(socketWrapper, event);
                    }

方法再次跳转进入了

 

@Override
        public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
            if (getLog().isDebugEnabled()) {
                getLog().debug(sm.getString("abstractConnectionHandler.process",
                        wrapper.getSocket(), status));
            }
            ..........


                do {
                    //此处是重点方法调用
                    state = processor.process(wrapper, status);
                    .........
                
                }

.......

}

随后进入了 AbstractProcessorLight 类的 process(SocketWrapperBase<?> socketWrapper, SocketEvent status) 方法。

后再经过调用便进入

CoyoteAdapter类的service方法调用

此方法创建了Request、 Response,并且对Request进行了解析。

 @Override
    public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
            throws Exception {

        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);

        if (request == null) {
            // Create objects
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);

            // Link objects
            request.setResponse(response);
            response.setRequest(request);

            // Set as notes
            req.setNote(ADAPTER_NOTES, request);
            res.setNote(ADAPTER_NOTES, response);

            // Set query string encoding
            req.getParameters().setQueryStringCharset(connector.getURICharset());
        }

        if (connector.getXpoweredBy()) {
            response.addHeader("X-Powered-By", POWERED_BY);
        }

        boolean async = false;
        boolean postParseSuccess = false;

        req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());

        try {
            // 解析 Request
            // Parse and set Catalina and configuration specific
            // request parameters
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) { //解析成功后进入 Sevlet的调用过程。
                //check valves if we support async
                request.setAsyncSupported(
                        connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
            }
            if (request.isAsync()) {
                async = true;
                ReadListener readListener = req.getReadListener();
                if (readListener != null && request.isFinished()) {
                    // Possible the all data may have been read during service()
                    // method so this needs to be checked here
                    ClassLoader oldCL = null;
                    try {
                        oldCL = request.getContext().bind(false, null);
                        if (req.sendAllDataReadEvent()) {
                            req.getReadListener().onAllDataRead();
                        }
                    } finally {
                        request.getContext().unbind(false, oldCL);
                    }
                }

                Throwable throwable =
                        (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

                // If an async request was started, is not going to end once
                // this container thread finishes and an error occurred, trigger
                // the async error process
                if (!request.isAsyncCompleting() && throwable != null) {
                    request.getAsyncContextInternal().setErrorState(throwable, true);
                }
            } else {
                request.finishRequest();
                response.finishResponse();
            }

        } catch (IOException e) {
            // Ignore
        } finally {
            AtomicBoolean error = new AtomicBoolean(false);
            res.action(ActionCode.IS_ERROR, error);

            if (request.isAsyncCompleting() && error.get()) {
                // Connection will be forcibly closed which will prevent
                // completion happening at the usual point. Need to trigger
                // call to onComplete() here.
                res.action(ActionCode.ASYNC_POST_PROCESS,  null);
                async = false;
            }

            // Access log
            if (!async && postParseSuccess) {
                // Log only if processing was invoked.
                // If postParseRequest() failed, it has already logged it.
                Context context = request.getContext();
                Host host = request.getHost();
                // If the context is null, it is likely that the endpoint was
                // shutdown, this connection closed and the request recycled in
                // a different thread. That thread will have updated the access
                // log so it is OK not to update the access log here in that
                // case.
                // The other possibility is that an error occurred early in
                // processing and the request could not be mapped to a Context.
                // Log via the host or engine in that case.
                long time = System.currentTimeMillis() - req.getStartTime();
                if (context != null) {
                    context.logAccess(request, response, time, false);
                } else if (response.isError()) {
                    if (host != null) {
                        host.logAccess(request, response, time, false);
                    } else {
                        connector.getService().getContainer().logAccess(
                                request, response, time, false);
                    }
                }
            }

            req.getRequestProcessor().setWorkerThreadName(null);

            // Recycle the wrapper request and response
            if (!async) {
                updateWrapperErrorCount(request, response);
                request.recycle();
                response.recycle();
            }
        }
    }

解析了Request中的一些信息后,并且解析成功则进入下面方法的调用:

if (postParseSuccess) {
                //check valves if we support async
                request.setAsyncSupported(
                        connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
            }

此处便开始了   四个主要容器的执行流程   。

具体如何通过容器 一步步 进入SpringMVC 完成业务代码的,下篇文章单独讲解吧。

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值