TOMCAT8源碼分析——處理請求分析(下)


前言

  本文繼續講解TOMCAT的請求原理分析,建議朋友們閱讀本文時首先閱讀過《TOMCAT源碼分析——請求原理分析(上)》和《TOMCAT源碼分析——請求原理分析(中)》。在《TOMCAT源碼分析——請求原理分析(中)》一文我簡單講到了Pipeline,但並未完全展開,本文將從Pipeline開始講解請求原理的剩余內容。

管道

  在Tomcat中管道Pipeline是一個接口,定義了使得一組閥門Valve按照順序執行的規范,Pipeline中定義的接口如下:

  • getBasic:獲取管道的基礎閥門;
  • setBasic:設置管道的基礎閥門;
  • addValve:添加閥門;
  • getValves:獲取閥門集合;
  • removeValve:移除閥門;
  • getFirst:獲取第一個閥門;
  • isAsyncSupported:當管道中的所有閥門都支持異步時返回ture,否則返回false;
  • getContainer:獲取管道相關聯的容器,比如StandardEngine;
  • setContainer:設置管道相關聯的容器。

  Engine、Host、Context及Wrapper等容器都定義了自身的Pipeline,每個Pipeline都包含一到多個Valve。Valve定義了各個閥門的接口規范,其類繼承體系如圖1所示。

圖1  Valve的類繼承體系

tomcat管道示意圖

這里對圖1中的主要部分(LifecycleMBeanBase及Contained接口在《TOMCAT源碼分析——生命周期管理》一文詳細闡述)進行介紹:

  • Valve:定義了管道中閥門的接口規范,getNext和setNext分別用於獲取或者設置當前閥門的下游閥門,invoke方法用來應用當前閥門的操作。
  • ValveBase:Valve接口的基本實現,ValveBase與Valve的具體實現采用抽象模板模式將管道中的閥門串聯起來。
  • StandardEngineValve:StandardEngine中的唯一閥門,主要用於從request中選擇其host映射的Host容器StandardHost。
  • AccessLogValve:StandardHost中的第一個閥門,主要用於管道執行結束之后記錄日志信息。
  • ErrorReportValve:StandardHost中緊跟AccessLogValve的閥門,主要用於管道執行結束后,從request對象中獲取異常信息,並封裝到response中以便將問題展現給訪問者。
  • StandardHostValve:StandardHost中最后的閥門,主要用於從request中選擇其context映射的Context容器StandardContext以及訪問request中的Session以更新會話的最后訪問時間。
  • StandardContextValve:StandardContext中的唯一閥門,主要作用是禁止任何對WEB-INF或META-INF目錄下資源的重定向訪問,對應用程序熱部署功能的實現,從request中獲得StandardWrapper。
  • StandardWrapperValve:StandardWrapper中的唯一閥門,主要作用包括調用StandardWrapper的loadServlet方法生成Servlet實例和調用ApplicationFilterFactory生成Filter鏈。

  有了以上對Tomcat的管道設計的講述,我們下面詳細剖析其實現。

  在《TOMCAT源碼分析——請求原理分析(中)》一文中講到執行管道的代碼如代碼清單1所示。

代碼清單1

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

代碼清單1中的getContainer方法獲取到的實際是StandardService中的StandardEngine容器,根據《TOMCAT源碼分析——生命周期管理》一文的內容,我們知道StandardEngine繼承自ContainerBase,所以這里的getPipeline方法實際是ContainerBase實現的,代碼如下:

/**
     * Return the Pipeline object that manages the Valves associated with
     * this Container.
     */
    @Override
    public Pipeline getPipeline() {

        return (this.pipeline);

    }

pipeline在ContainerBase實例化時生成,代碼如下:

/**
     * The Pipeline object with which this Container is associated.
     */
    protected final Pipeline pipeline = new StandardPipeline(this);

代碼清單1隨后調用了StandardPipeline的getFirst方法(見代碼清單3)用來獲取管道中的第一個Valve ,由於Tomcat並沒有為StandardEngine的StandardPipeline設置first,因此將返回StandardPipeline的basic。

代碼清單3

復制代碼
    public Valve getFirst() {
        if (first != null) {
            return first;
        }
        
        return basic;
    }
復制代碼

 代碼清單3中的basic的類型是StandardEngineValve,那么它是何時添加到StandardEngine的StandardPipeline中的呢?還記得《TOMCAT源碼分析——SERVER.XML文件的加載與解析》一文在介紹過的ObjectCreateRule?在執行ObjectCreateRule的begin方法時,會反射調用StandardEngine的構造器生成StandardEngine的實例,StandardEngine的構造器中就會給其StandardPipeline設置basic為StandardEngineValve,見代碼清單4。

代碼清單4

    /**
     * Create a new StandardEngine component with the default basic Valve.
     */
    public StandardEngine() {

        super();
        pipeline.setBasic(new StandardEngineValve());
        /* Set the jmvRoute using the system property jvmRoute */
        try {
            setJvmRoute(System.getProperty("jvmRoute"));
        } catch(Exception ex) {
        }
        // By default, the engine will hold the reloading thread
        backgroundProcessorDelay = 10;

    }

代碼清單1中最后調用了StandardEngineValve的invoke方法(見代碼清單5)正式將請求交給管道處理。根據《TOMCAT源碼分析——請求原理分析(中)》一文對CoyoteAdapter.postParseRequest方法的介紹,request已經被映射到相對應的Context容器(比如/manager)。所以此處調用request的getHost方法,實質是從request屬性的mappingData對象中的屬性host得到的(見代碼清單6)。然后調用Host容器的Pipeline的getFirst方法獲得AbstractAccessLogValve。AbstractAccessLogValve的invoke方法(見代碼清單7),從中可以看出調用了getNext方法獲取Host容器的Pipeline的下一個ErrorReportValve,並調用其invoke方法,最后ErrorReportValve調用getNext方法獲取Host容器的Pipeline的下一個StandardHostValve。

代碼清單5

    @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) {
            response.sendError
                (HttpServletResponse.SC_BAD_REQUEST,
                 sm.getString("standardEngine.noHost", 
                              request.getServerName()));
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        host.getPipeline().getFirst().invoke(request, response);

    }

代碼清單6

/**
     * @return the Host within which this Request is being processed.
     */
    public Host getHost() {
        return mappingData.host;
    }
其中mappingData在初始化的時候時沒有值的,代碼如下Request.java:

/**
* Mapping data.
*/
protected final MappingData mappingData = new MappingData();

只有用戶請求的時候,調用CoyoteAdapter的service方法,該方法里面調用postParseRequest方法,該方法解析請求url,最后把host屬性賦值給mappingData,代碼如下Mapper.java
private final void internalMap(CharChunk host, CharChunk uri,
            String version, MappingData mappingData) throws IOException {

        if (mappingData.host != null) {
            // The legacy code (dating down at least to Tomcat 4.1) just
            // skipped all mapping work in this case. That behaviour has a risk
            // of returning an inconsistent result.
            // I do not see a valid use case for it.
            throw new AssertionError();
        }

        uri.setLimit(-1);

        // Virtual host mapping
        MappedHost[] hosts = this.hosts;
        MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
        if (mappedHost == null) {
            // Note: Internally, the Mapper does not use the leading * on a
            //       wildcard host. This is to allow this shortcut.
            int firstDot = host.indexOf('.');
            if (firstDot > -1) {
                int offset = host.getOffset();
                try {
                    host.setOffset(firstDot + offset);
                    mappedHost = exactFindIgnoreCase(hosts, host);
                } finally {
                    // Make absolutely sure this gets reset
                    host.setOffset(offset);
                }
            }
            if (mappedHost == null) {
                mappedHost = defaultHost;
                if (mappedHost == null) {
                    return;
                }
            }
        }
        mappingData.host = mappedHost.object;

        // Context mapping
        ContextList contextList = mappedHost.contextList;
        MappedContext[] contexts = contextList.contexts;
        int pos = find(contexts, uri);
        if (pos == -1) {
            return;
        }

代碼清單7

    @Override
    public void invoke(Request request, Response response) throws IOException,
            ServletException {
        final String t1Name = AccessLogValve.class.getName()+".t1";
        if (getState().isAvailable() && getEnabled()) {                
            // Pass this request on to the next valve in our pipeline
            long t1 = System.currentTimeMillis();
            boolean asyncdispatch = request.isAsyncDispatching();
            if (!asyncdispatch) {
                request.setAttribute(t1Name, new Long(t1));
            }
    
            getNext().invoke(request, response);
    
            //we're not done with the request
            if (request.isAsyncDispatching()) {
                return;
            } else if (asyncdispatch && request.getAttribute(t1Name)!=null) {
                t1 = ((Long)request.getAttribute(t1Name)).longValue();
            }
            
            long t2 = System.currentTimeMillis();
            long time = t2 - t1;

            log(request,response, time);
        } else
            getNext().invoke(request, response);       
    }

 根據以上分析,我們看到StandardEngine容器的Pipeline中只有一個Valve(StandardEngineValve),而StandardHost容器中有三個Valve(分別是AccessLogValve、ErrorReportValve和StandardHostValve),此外StandardContext容器中有一個Valve(StandardContextValve),StandardWrapper中也只有一個Valve(StandardWrapperValve)。這些閥門Valve通過invoke方法彼此串聯起來,最終構成的執行順序十分類似於一個管道,最終形成的管道正如圖2一樣,這也許是Pipeline名字的由來。

圖2  Tomcat管道示意圖

本文以StandardEngineValve和AccessLogValve為例講了Valve的實現,以及Pipeline是如何串聯起來的,我們最后看看StandardWrapperValve的實現,其它Valve的實現不再贅述。

FILTER與職責鏈模式

  根據對管道和閥門的分析, 我們知道要分析StandardWrapperValve,只需直接閱讀其invoke方法即可,見代碼清單8所示。

代碼清單8

    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Initialize local variables we may need
        boolean unavailable = false;
        Throwable throwable = null;
        // This should be a Request attribute...
        long t1=System.currentTimeMillis();
        requestCount++;
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        Servlet servlet = null;
        Context context = (Context) wrapper.getParent();
        
        // 省略校驗及次要代碼

        // Allocate a servlet instance to process this request
        try {
            if (!unavailable) {
                servlet = wrapper.allocate();
            }
        } catch (UnavailableException e) {
            // 省略異常處理代碼
        } catch (ServletException e) {
            // 省略異常處理代碼
        } catch (Throwable e) {
            // 省略異常處理代碼
        }

        // Identify if the request is Comet related now that the servlet has been allocated
        boolean comet = false;
        if (servlet instanceof CometProcessor 
                && request.getAttribute("org.apache.tomcat.comet.support") == Boolean.TRUE) {
            comet = true;
            request.setComet(true);
        }
        
        // Acknowledge the request
        try {
            response.sendAcknowledgement();
        } catch (IOException e) {
            // 省略異常處理代碼
        } catch (Throwable e) {
            // 省略異常處理代碼
        }
        MessageBytes requestPathMB = request.getRequestPathMB();
        DispatcherType dispatcherType = DispatcherType.REQUEST;
        if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC; 
        request.setAttribute
            (ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
             dispatcherType);
        request.setAttribute
            (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
             requestPathMB);
        // Create the filter chain for this request
        ApplicationFilterFactory factory =
            ApplicationFilterFactory.getInstance();
        ApplicationFilterChain filterChain =
            factory.createFilterChain(request, wrapper, servlet);
        
        // Reset comet flag value after creating the filter chain
        request.setComet(false);

        // Call the filter chain for this request
        // NOTE: This also calls the servlet's service() method
        try {
            String jspFile = wrapper.getJspFile();
            if (jspFile != null)
                request.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
            else
                request.removeAttribute(Globals.JSP_FILE_ATTR);
            if ((servlet != null) && (filterChain != null)) {
                // Swallow output if needed
                if (context.getSwallowOutput()) {
                    try {
                        SystemLogHandler.startCapture();
                        if (request.isAsyncDispatching()) {
                            //TODO SERVLET3 - async
                            ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch(); 
                        } else if (comet) {
                            filterChain.doFilterEvent(request.getEvent());
                            request.setComet(true);
                        } else {
                            filterChain.doFilter(request.getRequest(), 
                                    response.getResponse());
                        }
                    } finally {
                        String log = SystemLogHandler.stopCapture();
                        if (log != null && log.length() > 0) {
                            context.getLogger().info(log);
                        }
                    }
                } else {
                    if (request.isAsyncDispatching()) {
                        //TODO SERVLET3 - async
                        ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
                    } else if (comet) {
                        request.setComet(true);
                        filterChain.doFilterEvent(request.getEvent());
                    } else {
                        filterChain.doFilter
                            (request.getRequest(), response.getResponse());
                    }
                }

            }
            request.removeAttribute(Globals.JSP_FILE_ATTR);
        } catch (ClientAbortException e) {
            // 省略異常處理代碼
        } catch (IOException e) {
            // 省略異常處理代碼
        } catch (UnavailableException e) {
            // 省略異常處理代碼
        } catch (ServletException e) {
            // 省略異常處理代碼
        } catch (Throwable e) {
            // 省略異常處理代碼
        }

        // Release the filter chain (if any) for this request
        if (filterChain != null) {
            if (request.isComet()) {
                // If this is a Comet request, then the same chain will be used for the
                // processing of all subsequent events.
                filterChain.reuse();
            } else {
                filterChain.release();
            }
        }

        // Deallocate the allocated servlet instance
        try {
            if (servlet != null) {
                wrapper.deallocate(servlet);
            }
        } catch (Throwable e) {
            // 省略異常處理代碼
            }
        }

        // If this servlet has been marked permanently unavailable,
        // unload it and release this instance
        try {
            if ((servlet != null) &&
                (wrapper.getAvailable() == Long.MAX_VALUE)) {
                wrapper.unload();
            }
        } catch (Throwable e) {
            // 省略異常處理代碼
        }
        long t2=System.currentTimeMillis();

        long time=t2-t1;
        processingTime += time;
        if( time > maxTime) maxTime=time;
        if( time < minTime) minTime=time;

    }

 通過閱讀代碼清單8,我們知道StandardWrapperValve的invoke方法的執行步驟如下:

  1. 調用StandardWrapper的allocate方法分配org.apache.catalina.servlets.DefaultServlet的實例處理訪問包括*.html、*.htm、*.gif、*.jpg、*.jpeg等資源的request,分配org.apache.jasper.servlet.JspServlet的實例處理訪問*.jpg頁面的request。簡單提下這些Servlet實例是在StandardContext啟動的時候調用StandardWrapper的load方法用反射生成的,有關StandardContext啟動的內容可以參考《TOMCAT源碼分析——生命周期管理》一文。
  2. 確認當前request是否是Comet的,由於默認的DefaultServlet並未實現CometProcessor接口,所以不會作為Comet的請求處理。順便簡單提下,Comet 指的是一種 Web 應用程序的架構。在這種架構中,客戶端程序(通常是瀏覽器)不需要顯式的向服務器端發出請求,服務器端會在其數據發生變化的時候主動的將數據異步的發送給客戶端,從而使得客戶端能夠及時的更新用戶界面以反映服務器端數據的變化。
  3. 向客戶端發送確認。
  4. 給request對象設置請求類型和請求路徑屬性。
  5. 獲取ApplicationFilterFactory(單例模式實現),並調用其createFilterChain方法創建ApplicationFilterChain。
  6. 調用ApplicationFilterChain的doFilter方法,執行ApplicationFilterChain中維護的Filter職責鏈。
  7. 調用ApplicationFilterChain的release方法清空對Servlet、Filter的引用。
  8. 調用StandardWrapper的deallocate方法釋放為其分配的Servlet。

注意:如果接收請求的Servlet實現了SingleThreadModel接口,那么singleThreadModel屬性為true,則Tomcat的StandardWrapper中只有一個Servlet實例,否則會創建一個Servlet實例池。

創建Filter職責鏈用到createFilterChain方法,其實現見代碼清單9。

代碼清單9

/**
     * Construct a FilterChain implementation that will wrap the execution of
     * the specified servlet instance.
     *
     * @param request The servlet request we are processing
     * @param wrapper The wrapper managing the servlet instance
     * @param servlet The servlet instance to be wrapped
     *
     * @return The configured FilterChain instance or null if none is to be
     *         executed.
     */
    public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {

        // If there is no servlet to execute, return null
        if (servlet == null)
            return null;

        // Create and initialize a filter chain object
        ApplicationFilterChain filterChain = null;
        if (request instanceof Request) {
            Request req = (Request) request;
            if (Globals.IS_SECURITY_ENABLED) {
                // Security: Do not recycle
                filterChain = new ApplicationFilterChain();
            } else {
                filterChain = (ApplicationFilterChain) req.getFilterChain();
                if (filterChain == null) {
                    filterChain = new ApplicationFilterChain();
                    req.setFilterChain(filterChain);
                }
            }
        } else {
            // Request dispatcher in use
            filterChain = new ApplicationFilterChain();
        }

        filterChain.setServlet(servlet);
        filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

        // Acquire the filter mappings for this Context
        StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();

        // If there are no filter mappings, we are done
        if ((filterMaps == null) || (filterMaps.length == 0))
            return (filterChain);

        // Acquire the information we will need to match filter mappings
        DispatcherType dispatcher =
                (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);

        String requestPath = null;
        Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
        if (attribute != null){
            requestPath = attribute.toString();
        }

        String servletName = wrapper.getName();

        // Add the relevant path-mapped filters to this filter chain
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMaps[i], requestPath))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // Add filters that match on servlet name second
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMaps[i], servletName))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // Return the completed filter chain
        return filterChain;
    }

根據代碼清單9,我們整理下整個創建Filter職責鏈的過程:

  1. 從request中獲取請求的類型(Tomcat目前提供的請求類型有REQUEST、FORWARD、INCLUDE、ASYNC及ERROR五種)與路徑;
  2. 創建ApplicationFilterChain並設置給當前request;
  3. 給ApplicationFilterChain設置Servlet,即DefaultServlet;
  4. 從StandardContext中獲取當前Context的filterMaps;
  5. 如果filterMaps為空,則說明當前Context沒有配置Filter,否則會將filterMaps中的Filter全部添加到ApplicationFilterChain中的Filter職責鏈中。

 

  調用ApplicationFilterChain的doFilter方法,執行ApplicationFilterChain中維護的Filter職責鏈。Filter職責鏈是對職責鏈模式的經典應用,我們先通過圖3來介紹其執行流程。

圖3  Tomcat的Filter職責鏈執行流程

這里對圖3的執行過程進行介紹:

  1. StandardWrapperValve的invoke方法在創建完ApplicationFilterChain后,第一次調用ApplicationFilterChain的doFilter方法;
  2. 如果ApplicationFilterChain自身維護的Filter數組中還有沒有執行的Filter,則取出此Filter並執行Filter的doFilter方法(即第3步),否則執行Servlet的service方法處理請求(即第4步);
  3. 每個Filter首先執行自身的過濾功能,最后在執行結束前會回調ApplicationFilterChain的doFilter方法,此時會將執行流程交給第2步;
  4. Servlet的service實際會調用自身的doGet、doHead、doPost、doPut、doDelete等方法。

第2步對應了圖3中M.N這個標記的M部分,第3步則對應N的部分。

本文最后從源碼實現級別分析Filter職責鏈的執行過程,首先來看ApplicationFilterChain的doFilter方法,見代碼清單10。

代碼清單10

@Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                else if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new ServletException(e.getMessage(), e);
            }
        } else {
            internalDoFilter(request,response);
        }
    }

從代碼清單10看到ApplicationFilterChain的doFilter方法主要調用了internalDoFilter方法(見代碼清單11)。

代碼清單11

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++];
            try {
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }

            if (request.isAsyncSupported() && !servletSupportsAsync) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();
                Object[] args = new Object[]{req, res};
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                servlet.service(request, response);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(sm.getString("filterChain.servlet"), e);
        } finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
    }

執行SERVLET

從代碼清單11,我們可以看到ApplicationFilterChain最后會執行Servlet的service方法,此service方法實際是所有Servlet的父類HttpServlet實現的,見代碼清單12。

代碼清單12

    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;
        
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }
}

代碼清單12中的service方法調用重載的service方法,后者通過判斷HttpServletRequest對象的HTTP Method,調用不同的方法,如GET、DELETE、POST等,見代碼清單13。

代碼清單13

    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);        
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
            
        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

以doGet方法為例,見代碼清單14。

代碼清單14

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }

不對啊!為什么doGet方法的實現只是返回了400和405錯誤呢?因為這是抽象類HttpServlet的默認實現,用戶必須實現自身的Servlet或者使用默認的DefaultServlet。

 

至此,Tomcat有關請求流程的主要內容已經講解完畢。歡迎大家提出寶貴意見!

如需轉載,請標明本文作者及出處——作者:jiaan.gja,本文原創首發:博客園,原文鏈接:http://www.cnblogs.com/jiaan-geng/p/4898871.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM