Tomcat中的容器是怎么處理請求的

這篇文章將為大家詳細(xì)講解有關(guān)Tomcat中的容器是怎么處理請求的,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

成都創(chuàng)新互聯(lián)公司-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比臨汾網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式臨汾網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋臨汾地區(qū)。費(fèi)用合理售后完善,10多年實(shí)體公司更值得信賴。

從Adapter中說起

我們繼續(xù)跟著上篇文章Adapter的源碼,繼續(xù)分析,上篇文章結(jié)尾的源碼如下:

  //源碼1.類:  CoyoteAdapter implements Adapter
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);
        postParseSuccess = postParseRequest(req, request, res, response);
            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);
            }
            
    }

上面的源碼的主要作用就是獲取到容器,然后調(diào)用getPipeline()獲取Pipeline,最后去invoke調(diào)用,我們來看看這個(gè)Pipeline是做什么的。

//源碼2.Pipeline接口
public interface Pipeline extends Contained {
  public Valve getBasic();
  public void setBasic(Valve valve);
  public void addValve(Valve valve);
  public Valve[] getValves();
  public void removeValve(Valve valve);
  public Valve getFirst();
  public boolean isAsyncSupported();
  public void findNonAsyncValves(Set<String> result);
}
//源碼3. Valve接口
public interface Valve {
 public Valve getNext();
 public void setNext(Valve valve);
 public void backgroundProcess();
 public void invoke(Request request, Response response)
        throws IOException, ServletException;
 public boolean isAsyncSupported();

我們從字面上可以理解Pipeline就是管道,而Valve就是閥門,實(shí)際上在Tomcat中的作用也是和字面意思差不多。每個(gè)容器都有一個(gè)管道,而管道中又有多個(gè)閥門。我們通過后面的分析來證明這一點(diǎn)。

管道-閥門(Pipeline-Valve)

我們看到上面的源碼是PipelineValve的接口,Pipeline主要是設(shè)置Valve,而Valve是一個(gè)鏈表,然后可以進(jìn)行invoke方法的調(diào)用。我們回顧下這段源碼:

//源碼4
connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);

這里是直接獲取容器的管道,然后獲取第一個(gè)Valve進(jìn)行調(diào)用。我們在之前提到過Valve是一個(gè)鏈表,這里只調(diào)用第一個(gè),也就是可以通過Next去調(diào)用到最后一個(gè)。我們再回顧下我們第一篇文章《Tomcat在SpringBoot中是如何啟動(dòng)的》中提到過,容器是分為4個(gè)子容器,分別為Engine、Host、Context、Wrapper,他們同時(shí)也是父級和子級的關(guān)系,Engine>Host>Context>Wrapper。

我之前提到過,每個(gè)容器都一個(gè)Pipeline,那么這個(gè)是怎么體現(xiàn)出來的呢?我們看容器的接口源碼就可以發(fā)現(xiàn),Pipeline是容器接口定義的一個(gè)基本屬性:

//源碼5.
public interface Container extends Lifecycle {
    //省略其他代碼
  /**
     * Return the Pipeline object that manages the Valves associated with
     * this Container.
     *
     * @return The Pipeline
     */
    public Pipeline getPipeline();
    
}

我們知道了每個(gè)容器都有一個(gè)管道(Pipeline),管道中有許多閥門(Valve),Valve可以進(jìn)行鏈?zhǔn)秸{(diào)用,那么問題來了,父容器管道中的Valve怎么調(diào)用到子容器中的Valve呢?在Pipeline的實(shí)現(xiàn)類StandardPipeline中,我們發(fā)現(xiàn)了如下源碼:

 /**
// 源碼6.
     * The basic Valve (if any) associated with this Pipeline.
     */
    protected Valve basic = null;
       /**
     * The first valve associated with this Pipeline.
     */
    protected Valve first = null;
    
     public void addValve(Valve valve) {

        //省略部分代碼

        // Add this Valve to the set associated with this Pipeline
        if (first == null) {
            first = valve;
            valve.setNext(basic);
        } else {
            Valve current = first;
            while (current != null) {
                //這里循環(huán)設(shè)置Valve,保證最后一個(gè)是basic
                if (current.getNext() == basic) {
                    current.setNext(valve);
                    valve.setNext(basic);
                    break;
                }
                current = current.getNext();
            }
        }

        container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
    }

根據(jù)如上代碼,我們知道了basic是一個(gè)管道(Pipeline)中的最后一個(gè)閥門,按道理只要最后一個(gè)閥門是下一個(gè)容器的第一個(gè)閥門就可以完成全部的鏈?zhǔn)秸{(diào)用了。我們用一個(gè)請求debug下看看是不是和我們的猜測一樣,我們在CoyoteAdapter中的service方法中打個(gè)斷點(diǎn),效果如下: Tomcat中的容器是怎么處理請求的

這里我們可以知道,在適配器調(diào)用容器的時(shí)候,也就是調(diào)用Engine的管道,只有一個(gè)閥門,也就是basic,值為StandardEngineValve。我們發(fā)現(xiàn)這個(gè)閥門的invoke方法如下:

//源碼7.
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);
    }

我們繼續(xù)debug查看結(jié)果如下: Tomcat中的容器是怎么處理請求的

所以這里的basic實(shí)際上將會(huì)調(diào)用到Host容器的管道(Pipeline)和閥門(Valve),也就是說,每個(gè)容器管道中的basic是負(fù)責(zé)調(diào)用下一個(gè)子容器的閥門。我用一張圖來表示:

Tomcat中的容器是怎么處理請求的

這張圖清晰的描述了,Tomcat內(nèi)部的容器是如何流轉(zhuǎn)請求的,從連接器(Connector)過來的請求會(huì)進(jìn)入Engine容器,Engine通過管道(Pieline)中的閥門(Valve)來進(jìn)行鏈?zhǔn)秸{(diào)用,最后的basic閥門是負(fù)責(zé)調(diào)用下一個(gè)容器的第一個(gè)閥門的,一直調(diào)用到Wrapper,然后Wrapper再執(zhí)行Servlet

我們看看Wrapper源碼,是否真的如我們所說:

//源碼8.
 public final void invoke(Request request, Response response)
        throws IOException, ServletException {
            //省略部分源碼
        Servlet servlet = null;
        if (!unavailable) {
            servlet = wrapper.allocate();
        }
            
        // Create the filter chain for this request
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
                
         filterChain.doFilter(request.getRequest(),
                                    response.getResponse());        
        }

看到這里,你可能會(huì)說這里明明只是創(chuàng)建了過濾器(Filter)并且去調(diào)用而已,并沒有去調(diào)用Servlet ,沒錯(cuò),這里確實(shí)沒有去調(diào)用Servlet,但是我們知道,過濾器(Filter)是在Servlet之前執(zhí)行的,也就是說,filterChain.doFilter執(zhí)行完之后變會(huì)執(zhí)行Servlet。我們看看ApplicationFilterChain的源碼是否如我們所說:

//源碼9.
 public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        //省略部分代碼
        internalDoFilter(request,response);
    }
//源碼10.  
 private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
        //省略部分代碼
        // Call the next filter if there is one
        if (pos < n) {
         //省略部分代碼
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = filterConfig.getFilter();
            filter.doFilter(request, response, this);
            return;
        }
        //調(diào)用servlet
        // We fell off the end of the chain -- call the servlet instance
        servlet.service(request, response);

通過源碼我們發(fā)現(xiàn),在調(diào)用完所有的過濾器(Filter)之后,servlet就開始調(diào)用service。我們看看servlet的實(shí)現(xiàn)類

Tomcat中的容器是怎么處理請求的

這里我們熟悉的HttpServletGenericServletTomcat包的類,實(shí)際上只有HttpServlet,因?yàn)?code>GenericServlet是HttpServlet的父類。后面就是移交給了框架去處理了,Tomcat內(nèi)部的請求已經(jīng)到此是完成了。

Tomcat的多應(yīng)用隔離實(shí)現(xiàn)

我們知道,Tomcat是支持部署多個(gè)應(yīng)用的,那么Tomcat是如何支持多應(yīng)用的部署呢?是怎么保證多個(gè)應(yīng)用之間不會(huì)混淆的呢?要想弄懂這個(gè)問題,我們還是要回到適配器去說起,回到service方法

//源碼11.類:CoyoteAdapter
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
            throws Exception {
            //省略部分代碼
            // Parse and set Catalina and configuration specific
            // request parameters
            //處理URL映射
            postParseSuccess = postParseRequest(req, request, res, response);
            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);
            }
}

我們在之前的源碼中只談到了connector.getService().getContainer().getPipeline().getFirst().invoke( request, response) 這段代碼,這部分代碼是調(diào)用容器,但是在調(diào)用容器之前有個(gè)postParseRequest方法是用來處理映射請求的,我們跟進(jìn)看看源碼:

//源碼12.類:CoyoteAdapter
 protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
            org.apache.coyote.Response res, Response response) throws IOException, ServletException {
        省略部分代碼
        boolean mapRequired = true;
         while (mapRequired) {
            // This will map the the latest version by default
            connector.getService().getMapper().map(serverName, decodedURI,
                    version, request.getMappingData());
            //沒有找到上下文就報(bào)404錯(cuò)誤        
            if (request.getContext() == null) {
                // Don't overwrite an existing error
                if (!response.isError()) {
                    response.sendError(404, "Not found");
                }
                // Allow processing to continue.
                // If present, the error reporting valve will provide a response
                // body.
                return true;
            }        
            }

這里就是循環(huán)去處理Url映射,如果Context沒有找到,就返回404錯(cuò)誤,我們繼續(xù)看源碼:

//源碼13.類:Mapper
public void map(MessageBytes host, MessageBytes uri, String version,
                    MappingData mappingData) throws IOException {

        if (host.isNull()) {
            String defaultHostName = this.defaultHostName;
            if (defaultHostName == null) {
                return;
            }
            host.getCharChunk().append(defaultHostName);
        }
        host.toChars();
        uri.toChars();
        internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
    }
    //源碼14.類:Mapper
 private final void internalMap(CharChunk host, CharChunk uri,
            String version, MappingData mappingData) throws IOException {
        //省略部分代碼
        // Virtual host mapping 處理Host映射
        MappedHost[] hosts = this.hosts;
        MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
      
         //省略部分代碼
        if (mappedHost == null) {
             mappedHost = defaultHost;
            if (mappedHost == null) {
                return;
            }
        }
    
        mappingData.host = mappedHost.object;
        
        // Context mapping 處理上下文映射
        ContextList contextList = mappedHost.contextList;
        MappedContext[] contexts = contextList.contexts;
        //省略部分代碼
        if (context == null) {
            return;
        }
        mappingData.context = contextVersion.object;
        mappingData.contextSlashCount = contextVersion.slashCount;

        // Wrapper mapping 處理Servlet映射
        if (!contextVersion.isPaused()) {
            internalMapWrapper(contextVersion, uri, mappingData);
        }

    }

由于上面的源碼比較多,我省略了很多代碼,保留了能理解主要邏輯的代碼,總的來說就是處理Url包括三部分,映射Host,映射Context和映射Servlet(為了節(jié)省篇幅,具體細(xì)節(jié)源碼請感興趣的同學(xué)自行研究)。

這里我們可以發(fā)現(xiàn)一個(gè)細(xì)節(jié),就是三個(gè)處理邏輯都是緊密關(guān)聯(lián)的,只有Host不為空才會(huì)處理Context,對于Servlet也是同理。所以這里我們只要Host配置不同,那么后面所有的子容器都是不同的,也就完成了應(yīng)用隔離的效果。但是對于SpringBoot內(nèi)嵌Tomcat方式(使用jar包啟動(dòng))來說,并不具備實(shí)現(xiàn)多應(yīng)用的模式,本身一個(gè)應(yīng)用就是一個(gè)Tomcat。

為了便于理解,我也畫了一張多應(yīng)用隔離的圖,這里我們假設(shè)有兩個(gè)域名admin.luozhou.comweb.luozhou.com 然后我每個(gè)域名下部署2個(gè)應(yīng)用,分別是User,log,blog,shop。那么當(dāng)我去想去添加用戶的時(shí)候,我就會(huì)請求admin.luozhou.com域名下的UserContext下面的add的Servlet(說明:這里例子設(shè)計(jì)不符合實(shí)際開發(fā)原則,add這種粒度應(yīng)該是框架中的controller完成,而不是Servlet)。

Tomcat中的容器是怎么處理請求的

總結(jié)

這篇文章我們研究了Tomcat中容器是如何處理請求的,我們來回顧下內(nèi)容:

  • 連接器把請求丟給適配器適配后調(diào)用容器(Engine)

  • 容器內(nèi)部是通過管道(Pieline)-閥門(Valve)模式完成容器的調(diào)用的,父容器調(diào)用子容器主要通過一個(gè)basic的閥門來完成的。

  • 最后一個(gè)子容器wrapper完成調(diào)用后就會(huì)構(gòu)建過濾器來進(jìn)行過濾器調(diào)用,調(diào)用完成后就到了Tomcat內(nèi)部的最后一步,調(diào)用servlet。也可以理解我們常用的HttpServlet,所有基于Servlet規(guī)范的框架在這里就進(jìn)入了框架流程(包括SpringBoot)。

  • 最后我們還分析了Tomcat是如何實(shí)現(xiàn)多應(yīng)用隔離的,通過多應(yīng)用的隔離分析,我們也明白了為什么Tomcat要設(shè)計(jì)如此多的子容器,多子容器可以根據(jù)需要完成不同粒度的隔離級別來實(shí)現(xiàn)不同的場景需求。

關(guān)于“Tomcat中的容器是怎么處理請求的”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯(cuò),請把它分享出去讓更多的人看到。

網(wǎng)站欄目:Tomcat中的容器是怎么處理請求的
鏈接分享:http://muchs.cn/article30/ihgoso.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化、網(wǎng)站排名、網(wǎng)站營銷、網(wǎng)站內(nèi)鏈微信小程序、標(biāo)簽優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)