TOMCAT 源碼分析 -- 一次請求

TOMCAT 源碼分析 – 一次請求

前語

​ 在上一篇源碼分析《TOMCAT源碼分析–啓動》中已經知道,Tomcat在啓動中,會通過NIO監聽端口,而真正去接收請求的是pollerThread.start()輪詢線程的啓動,那麼請求的入口應該是到NIO中,最後被輪詢線程發現並被處理,那麼自然就要去看Poller線程的run()方法,看其是如何處理。(PS:心中要有上一篇裏面的模塊架構圖,很重要!

端點接收請求

        // org.apache.tomcat.util.net.NioEndpoint.Poller#run
	    @Override
        public void run() {
            // Loop until destroy() is called
            // 一直循環,直到destroy方法被調用
            while (true) {

                boolean hasEvents = false;

                try {
                    if (!close) {
                        // 遍歷事件隊列判斷是否有事件(請求)待處理
                        hasEvents = events();
                        if (wakeupCounter.getAndSet(-1) > 0) {
                            // If we are here, means we have other stuff to do
                            // Do a non blocking select
                            // 如果在這個分支,說明有其他工作要做,這裏就不去做一個阻塞的select
                            keyCount = selector.selectNow();
                        } else {
                            keyCount = selector.select(selectorTimeout);
                        }
                        wakeupCounter.set(0);
                    }
                    
                } catch (Throwable x) {
                   
                }
                // ...
                while (iterator != null && iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
                    if (socketWrapper == null) {
                        iterator.remove();
                    } else {
                        // socketWrapper 包裝不爲空,將其取出,進行處理
                        iterator.remove();
                        processKey(sk, socketWrapper);
                    }
                }

                // Process timeouts
                timeout(keyCount,hasEvents);
            }

            getStopLatch().countDown();
        }

​ 端點一直進行循環,檢測有沒有可用的NIO準備就緒,如果有就拿去processKey消費掉。那麼看看它具體怎麼做的

// org.apache.tomcat.util.net.NioEndpoint.Poller#processKey

protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
            try {
                if (close) {
                    cancelledKey(sk, socketWrapper);
                } else if (sk.isValid() && socketWrapper != null) {
                    if (sk.isReadable() || sk.isWritable()) {
                        if (socketWrapper.getSendfileData() != null) {
                            processSendfile(sk, socketWrapper, false);
                        } else {
                            unreg(sk, socketWrapper, sk.readyOps());
                            boolean closeSocket = false;
                            // Read goes before write
                            if (sk.isReadable()) {
                                if (socketWrapper.readOperation != null) {
                                    if (!socketWrapper.readOperation.process()) {
                                        closeSocket = true;
                                    }
                                    
                                    // 最終進入下面這個分支
                                } else if (!processSocket(socketWrapper, SocketEvent.OPEN_READ, true)) {
                                    closeSocket = true;
                                }
                            }
                        }
                    }
                } else {

                }
            } catch (CancelledKeyException ckx) {

            } catch (Throwable t) {

            }
        }
// org.apache.tomcat.util.net.AbstractEndpoint#processSocket

public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase<S> sc = null;
            // 想從緩存處理器中取一個出來先
            if (processorCache != null) {
                sc = processorCache.pop();
            }
            // 如果沒取到則創建一個處理器
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            // 獲取線程池處理 -- 這裏也就是併發去處理這個處理器了,這個線程就自己回去處理NIO的監聽了。
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
                // 可以觀察到進入了這個分支
                executor.execute(sc);
            } else {
                // 如果沒有獲取到線程池就直接調用這個處理器的run方法
                sc.run();
            }
        } catch (RejectedExecutionException ree) {
        }
    	// ...
        return true;
    }
  • 從上面的執行過程可以看到他使用線程池提交了sc處理器,那麼就去看處理器的run方法,且從運行中可以看到這個sc實際爲NioEndPoint$SocketProcessor的實例,發現這個實例中並沒有run方法,但是其超類SocketProcessorBase中的run方法實際調用了實例的doRun方法
    	// org.apache.tomcat.util.net.NioEndpoint.SocketProcessor#doRun
	    @Override
        protected void doRun() {
            NioChannel socket = socketWrapper.getSocket();
            Poller poller = NioEndpoint.this.poller;
            try {
                int handshake = -1;
                if (handshake == 0) {
                    SocketState state = SocketState.OPEN;
                    // Process the request from this socket
                    // 很容器看出來,它將在下面獲取處理器進行處理這個請求
                    if (event == null) {
                        state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                    } else {
                        // 實際上,進的是這個分支,而event事件剛好也是上面的OPEN_READ
                        state = getHandler().process(socketWrapper, event);
                    }
                    
            } catch (CancelledKeyException cx) {
            } catch (Throwable t) {
                
            } finally {

            }
        }
  • 從上面獲取了處理器要進行處理,也就是上一篇中的coyote的端點EndPoint獲取Processor,並且調用他的process方法進行處理。這個處理方法十分長,它的主要邏輯就是Handler獲取真正的處理器Process,然後調用它的process方法處理
		// org.apache.coyote.AbstractProtocol.ConnectionHandler#process
		@Override
        public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
            // 獲取處理器 -- 實際本次請求當前處理器爲Null
            Processor processor = (Processor) wrapper.getCurrentProcessor();
            try {
                if (processor == null) {
                    // 獲取協議,需要通過協議去找對應的協議處理器 -- 這兒實際返回Null
                    String negotiatedProtocol = wrapper.getNegotiatedProtocol();
                    // ...
                }
                if (processor == null) {
                    // 還是null,就先去已經回收但還未釋放的處理器棧中彈出一個來
                    // 因爲已經使用過,所以這裏就返回了Http11Processor
                    processor = recycledProcessors.pop();

                }
                if (processor == null) {
                    // 如果還沒獲取到處理器,就根據協議去創建一個對應的處理器,第一次訪問時會如此
                    processor = getProtocol().createProcessor();
                    register(processor);
                }    
                do {
                    // 協議處理器開始處理包裝的請求
                    state = processor.process(wrapper, status);

                } while ( state == SocketState.UPGRADING);
        }
  • 可以發現找到處理器後,調用process處理,不過這個方法是超類中的方法,且Http11Processor未進行覆蓋
// org.apache.coyote.AbstractProcessorLight#process
@Override
    public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
            throws IOException {

        SocketState state = SocketState.CLOSED;
        Iterator<DispatchType> dispatches = null;
        do {
            if (dispatches != null) {
            } else if (status == SocketEvent.DISCONNECT) {
                // Do nothing here, just wait for it to get recycled
            } else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) {
                state = dispatch(status);
                state = checkForPipelinedData(state, socketWrapper);
            } else if (status == SocketEvent.OPEN_WRITE) {
                state = SocketState.LONG;
            } else if (status == SocketEvent.OPEN_READ) {
                // 根據DEBUG之前提到了,event的類型就是OPEN_READ,故進這個分支,進入核心的處理。
                state = service(socketWrapper);
            } else if (status == SocketEvent.CONNECT_FAIL) {
                logAccess(socketWrapper);
            } else {

                state = SocketState.CLOSED;
            }

        return state;
    }

​ 那麼state = service(socketWrapper);就是Http11Processor的核心處理了

// org.apache.coyote.http11.Http11Processor#service
@Override
    public SocketState service(SocketWrapperBase<?> socketWrapper)
        throws IOException {
        // ...
            // Process the request in the adapter
            if (getErrorState().isIoAllowed()) {
                try {
                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                    // 就是這一步去找適配器並進行處理
                    // 獲得CoyoteAdapter的實例
                    getAdapter().service(request, response);
                   
                    if(keepAlive && !getErrorState().isError() && !isAsync() &&
                            statusDropsConnection(response.getStatus())) {
                        setErrorState(ErrorState.CLOSE_CLEAN, null);
                    }
                }   catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("http11processor.request.process"), t);
                    // 500 - Internal Server Error
                    response.setStatus(500);
                    setErrorState(ErrorState.CLOSE_CLEAN, t);
                    getAdapter().log(request, response, 0);
                }
            }
    }

  • 這一步很關鍵,他去獲得了Adapter,也就是模塊圖中,要通過這個適配器,去進行將Request進行轉換封裝,最後調用Catalina進行處理的位置
// org.apache.catalina.connector.CoyoteAdapter#service
@Override
    public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
            throws Exception {

        // 將Request 進行轉換封裝
        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);

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

        try {
            // Parse and set Catalina and configuration specific
            // 解析並設置Catalina和特定於配置 -- 什麼意思呢,就是在這裏去找配置-映射了
            // request parameters
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                // 調用容器的管道,進行流水線處理了
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
            }
        }
    }
  • 看到這兒就清晰多了,他兩步走,第一步根據配置尋找映射,
  • 第二步,調用了Service容器的關掉進行處理這個請求,那麼根據套娃模式,他也必將通過一層又一層的相似調用進去,拭目以待~
// org.apache.catalina.connector.CoyoteAdapter#postParseRequest
protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
            org.apache.coyote.Response res, Response response) throws IOException, ServletException {

    // 獲得請求解碼後的地址
    MessageBytes undecodedURI = req.requestURI();
    
    while (mapRequired) {
            // This will map the the latest version by default
        	// 在容器中尋找映射
            connector.getService().getMapper().map(serverName, decodedURI,
                    version, request.getMappingData());
    }
}

// map方法最終進入這個超類
// org.apache.catalina.mapper.Mapper#map(org.apache.tomcat.util.buf.MessageBytes, org.apache.tomcat.util.buf.MessageBytes, java.lang.String, org.apache.catalina.mapper.MappingData)
public void map(MessageBytes host, MessageBytes uri, String version,
                    MappingData mappingData) throws IOException {
        // 將主機名和URI做映射

        // 內部映射 
        internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
    }


// org.apache.catalina.mapper.Mapper#internalMap
private final void internalMap(CharChunk host, CharChunk uri,
            String version, MappingData mappingData) throws IOException {

        // Context mappinp
    	// 在這一步執行完之後,在contextList中就已經可以看到配置的具體映射了
        ContextList contextList = mappedHost.contextList;
        MappedContext[] contexts = contextList.contexts;

        // Wrapper mapping
        if (!contextVersion.isPaused()) {
            // 將映射數據包裝
            internalMapWrapper(contextVersion, uri, mappingData);
        }

    }

// org.apache.catalina.mapper.Mapper#internalMapWrapper
private final void internalMapWrapper(ContextVersion contextVersion,
                                          CharChunk path,
                                          MappingData mappingData) throws IOException {
    // ...
	// 這兒的邏輯很長,但一直調式,發現他最後走了Rule 7
        // Rule 7 -- Default servlet
        // 默認的servlet
        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
            if (contextVersion.defaultWrapper != null) {
                mappingData.wrapper = contextVersion.defaultWrapper.object;
                mappingData.requestPath.setChars
                    (path.getBuffer(), path.getStart(), path.getLength());
                mappingData.wrapperPath.setChars
                    (path.getBuffer(), path.getStart(), path.getLength());
                mappingData.matchType = MappingMatch.DEFAULT;
            }
            // Redirection to a folder
            char[] buf = path.getBuffer();
            if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
                String pathStr = path.toString();
                // Note: Check redirect first to save unnecessary getResource()
                //       call. See BZ 62968.
                if (contextVersion.object.getMapperDirectoryRedirectEnabled()) {
                    WebResource file;
                    // Handle context root
                    if (pathStr.length() == 0) {
                        file = contextVersion.resources.getResource("/");
                    } else {
                        file = contextVersion.resources.getResource(pathStr);
                    }
                    if (file != null && file.isDirectory()) {
                        // Note: this mutates the path: do not do any processing
                        // after this (since we set the redirectPath, there
                        // shouldn't be any)
                        path.setOffset(pathOffset);
                        path.append('/');
                        mappingData.redirectPath.setChars
                            (path.getBuffer(), path.getStart(), path.getLength());
                    } else {
                        mappingData.requestPath.setString(pathStr);
                        mappingData.wrapperPath.setString(pathStr);
                    }
                } else {
                    mappingData.requestPath.setString(pathStr);
                    mappingData.wrapperPath.setString(pathStr);
                }
            }
        }
}

​ 把映射對象包裝好後,進入第二步,去套娃中挖掘具體的處理:

                // Calling the container
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
// org.apache.catalina.core.StandardEngineValve#invoke
 @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Select the Host to be used for this Request
        Host host = request.getHost();
        if (host == null) {
            // HTTP 0.9 or HTTP 1.0 request without a host when no default host
            // is defined. This is handled by the CoyoteAdapter.
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        // 要求主機處理此請求
        host.getPipeline().getFirst().invoke(request, response);
    }
  • 可以看到進入了StandardEngineValve類中,那麼根據上一篇的模塊架構圖,以及server.xml的配置就可以知道,它將一層一層處理管道線,一層一層的進行invoke。那麼中間省略他套娃式的幾步invoke,走到了下面StandardContextValve的代碼
// org.apache.catalina.core.StandardContextValve#invoke
    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
		// ...
        // 管道處理
        // 此時他已經一層一層的走進來,一直走到包裝了servlet的wrapper
        wrapper.getPipeline().getFirst().invoke(request, response);
    }

​ 那麼看看這個wrapper的值是什麼:StandardEngine[Catalina].StandardHost[localhost].StandardContext[/webmvc].StandardWrapper[springmvc],可以發現引擎是“Catalina”,虛擬主機是“localhost”,上下文是“webmvc”,進行處理的servlet是“springmvc”,那麼就沒錯了。放入tomcat的webapps目錄中的樣例工程,就是這個上下文,以及spring mvc的servlet入口。

​ 接下來,有了對應的wrapper之後,它還經過了過濾鏈進行一定的過濾filter,最終在下面這個地方進行調用:

// org.apache.catalina.core.ApplicationFilterChain#internalDoFilter
private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
                // 真正調用servlet對請求做處理的位置,如果是spring mvc 這裏就是dispatcherServlet對象
                servlet.service(request, response);
       
        }
  • DEBUG可以看到這個servlet實例的值是DispatcherServlet,同時也可以看到它的上下文對象是spring的上下文,如下圖:

在這裏插入圖片描述

那麼,接下來請求的處理就是調用spring mvc進行完成了,tomcat對請求的處理分發就完成了!

結語

Tomcat的一次請求走的代碼看起來很長,實際上了解其架構之後,邏輯還是十分清晰的。

本次在Tomcat源碼的入門學習中,有以下體會:

  1. Tomcat的架構清晰,多層嵌套,看起來複雜,實際上就是剝洋蔥的行爲,看源碼的過程根據DEBUG來追蹤就可以清晰的一層一層的進去。
  2. 看源碼DEBUG真的很重要!
  3. 瞭解架構,從宏觀出發,不抓細枝末節,是快速看完源碼,瞭解主要思想的重要途徑。過分追求每一行代碼的行爲容易忘了自己前面看過的,以及你到底想看的是什麼。
  4. 瞭解完請求過程,那麼對一次請求最終調用之前打一個Log日誌,做統計等等都是可以完成的了~

推廣

推廣我一分錢也沒得賺!!!!

喝水不忘打井人吧,這個是在拉勾教育上看的《Tomcat核心源碼剖析》,應癲出品。只有三天的課程,第三天還會推薦你報班(手動滑稽~)。

槽點: 這是個“訓練營”課程,18元人民幣,有效期18天。到期就沒得看了,也督促我三天看完,五天內完成筆記~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章