Tomcat對HTTP請求的處理(三)


摘要:本文主要介紹了tomcat內部處理HTTP請求的Container部分,即剩余的部分

上一篇文章講到CoyoteAdapter對HTTP請求的處理,主要查看了postParseRequest()方法對request的處理填充。我們繼續往下看:

//代碼清單1
// Parse and set Catalina and configuration specific
// request parameters
req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
    //check valves if we support async
    request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
    // Calling the container
	//111
    connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
	//..略代碼
}

上一篇文章分析過了,標注1的地方最終調用的是StandardEngineValveinvoke()方法:

//代碼清單2
@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的pipeline 來處理
	//11111111
    host.getPipeline().getFirst().invoke(request, response);

}

在清單2的標注1的地方我們可以看到最后調用的是hostpipeline來處理,而StandardHostStandardEngine則有所不同,不同的地方在於,StandardEngine只有一個基本閥也就是StandardEngineValve,而StandardHost除了基本閥門StandardHostValve還額外有兩個閥門分別是AccessLogValveErrorReportValve。這兩個閥門的來源分別是server.xml中配置以及在StandardHoststartInternal()方法中添加。所以標注1的地方getFirst()返回的應該是AccessLogValve這個類的實例,至於為什么是AccessLogValve不是ErrorReportValve,這個大家可以自己思考下,下面我們繼續查看AccessLogValveinvoke()方法:

//代碼清單3
@Override
public void invoke(Request request, Response response) throws IOException,
        ServletException {
    getNext().invoke(request, response);
}

這里的getNext()返回的應該是ErrorReportValve,繼續查看其invoke()方法:

//代碼清單4
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
	//111111
    // Perform the request
    getNext().invoke(request, response);

    if (response.isCommitted()) {
        if (response.setErrorReported()) {
            // Error wasn't previously reported but we can't write an error
            // page because the response has already been committed. Attempt
            // to flush any data that is still to be written to the client.
            try {
                response.flushBuffer();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
            }
            // Close immediately to signal to the client that something went
            // wrong
            response.getCoyoteResponse().action(ActionCode.CLOSE_NOW, null);
        }
        return;
    }

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

    // If an async request is in progress and is not going to end once this
    // container thread finishes, do not trigger error page handling - it
    // will be triggered later if required.
    if (request.isAsync() && !request.isAsyncCompleting()) {
        return;
    }

    if (throwable != null && !response.isError()) {
        // Make sure that the necessary methods have been called on the
        // response. (It is possible a component may just have set the
        // Throwable. Tomcat won't do that but other components might.)
        // These are safe to call at this point as we know that the response
        // has not been committed.
        response.reset();
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }

    // One way or another, response.sendError() will have been called before
    // execution reaches this point and suspended the response. Need to
    // reverse that so this valve can write to the response.
    response.setSuspended(false);

    try {
        report(request, response, throwable);
    } catch (Throwable tt) {
        ExceptionUtils.handleThrowable(tt);
    }
}

可以看到在方法一開始也就是標注1的地方繼續是調用getNext()然后調用其invoke()方法,下面的代碼可以考慮為后續處理,所以我們繼續往下看,也就是StandardHostValveinvoke()方法:

//代碼清單5
@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // Select the Context to be used for this Request
	// 獲取處理這個request的context對象
    Context context = request.getContext();
    if (context == null) {
        response.sendError
            (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
             sm.getString("standardHost.noContext"));
        return;
    }

    // Bind the context CL to the current thread
    if( context.getLoader() != null ) {
        // Not started - it should check for availability first
        // This should eventually move to Engine, it's generic.
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                    context.getLoader().getClassLoader());
            AccessController.doPrivileged(pa);                
        } else {
            Thread.currentThread().setContextClassLoader
                    (context.getLoader().getClassLoader());
        }
    }
    if (request.isAsyncSupported()) {
        request.setAsyncSupported(context.getPipeline().isAsyncSupported());
    }

    boolean asyncAtStart = request.isAsync(); 
    boolean asyncDispatching = request.isAsyncDispatching();
    if (asyncAtStart || context.fireRequestInitEvent(request)) {

        // Ask this Context to process this request. Requests that are in
        // async mode and are not being dispatched to this resource must be
        // in error and have been routed here to check for application
        // defined error pages.
        try {
            if (!asyncAtStart || asyncDispatching) {
				//1111111
				//調用Context的pipeline來處理
                context.getPipeline().getFirst().invoke(request, response);
            } else {
                // Make sure this request/response is here because an error
                // report is required.
                if (!response.isErrorReportRequired()) {
                    throw new IllegalStateException(sm.getString("standardHost.asyncStateError"));
                }
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            container.getLogger().error("Exception Processing " + request.getRequestURI(), t);
            // If a new error occurred while trying to report a previous
            // error allow the original error to be reported.
            if (!response.isErrorReportRequired()) {
                request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
                throwable(request, response, t);
            }
        }

        // Now that the request/response pair is back under container
        // control lift the suspension so that the error handling can
        // complete and/or the container can flush any remaining data
        response.setSuspended(false);

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

        // Protect against NPEs if the context was destroyed during a
        // long running request.
        if (!context.getState().isAvailable()) {
            return;
        }

        // Look for (and render if found) an application level error page
        if (response.isErrorReportRequired()) {
            if (t != null) {
                throwable(request, response, t);
            } else {
                status(request, response);
            }
        }

        if (!request.isAsync() && (!asyncAtStart || !response.isErrorReportRequired())) {
            context.fireRequestDestroyEvent(request);
        }
    }

    // Access a session (if present) to update last accessed time, based on a
    // strict interpretation of the specification
    if (ACCESS_SESSION) {
        request.getSession(false);
    }

    // Restore the context classloader
    if (Globals.IS_SECURITY_ENABLED) {
        PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                StandardHostValve.class.getClassLoader());
        AccessController.doPrivileged(pa);                
    } else {
        Thread.currentThread().setContextClassLoader
                (StandardHostValve.class.getClassLoader());
    }
}

代碼比較長,先獲取了該需要處理該request的Context實例,然后調用了該實例的pipeline來處理request。而StandardContext對象在初始化的時候如果沒有在server.xml中配置Valve閥門的話,那么ContextgetFirst()方法返回的是StandardContextValve的實例,所以查看StandardContextValveinvoke()方法:

//代碼清單6
@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // Disallow any direct access to resources under WEB-INF or META-INF
    MessageBytes requestPathMB = request.getRequestPathMB();
    if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
            || (requestPathMB.equalsIgnoreCase("/META-INF"))
            || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
            || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // Select the Wrapper to be used for this Request
    Wrapper wrapper = request.getWrapper();
    if (wrapper == null || wrapper.isUnavailable()) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // Acknowledge the request
    try {
        response.sendAcknowledgement();
    } catch (IOException ioe) {
        container.getLogger().error(sm.getString(
                "standardContextValve.acknowledgeException"), ioe);
        request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return;
    }
    
    if (request.isAsyncSupported()) {
        request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
    }
	//調用wrapper的pipeline來處理
	//11111
    wrapper.getPipeline().getFirst().invoke(request, response);
}

從標注1的地方可以看到最終調用的還是子容器StandardWrapperpipeline來處理request,也就是StandardWrapperValveinvoke()方法:

//代碼清單7
@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();
    
    // Check for the application being marked unavailable
    if (!context.getState().isAvailable()) {
        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                       sm.getString("standardContext.isUnavailable"));
        unavailable = true;
    }
	
    // Check for the servlet being marked unavailable
    if (!unavailable && wrapper.isUnavailable()) {
        container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
                wrapper.getName()));
        long available = wrapper.getAvailable();
        if ((available > 0L) && (available < Long.MAX_VALUE)) {
            response.setDateHeader("Retry-After", available);
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                    sm.getString("standardWrapper.isUnavailable",
                            wrapper.getName()));
        } else if (available == Long.MAX_VALUE) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND,
                    sm.getString("standardWrapper.notFound",
                            wrapper.getName()));
        }
        unavailable = true;
    }

    // Allocate a servlet instance to process this request
    try {
        if (!unavailable) {
            //加載servlet
			//111111111111
            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 && Boolean.TRUE.equals(request.getAttribute(
            Globals.COMET_SUPPORTED_ATTR))) {
        comet = true;
        request.setComet(true);
    }
    
    MessageBytes requestPathMB = request.getRequestPathMB();
    DispatcherType dispatcherType = DispatcherType.REQUEST;
    if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC; 
    request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
    request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
            requestPathMB);
    // Create the filter chain for this request
	//2222222 創建filterChain
    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 {
        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 {
					//3333333333 調用fiterChain來處理 request 和 response
                    filterChain.doFilter(request.getRequest(), response.getResponse());
                }
            }

        }
    } catch (ClientAbortException e) {
        //異常處理 略
        exception(request, response, 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 {
			//444444444 釋放過濾器鏈
            filterChain.release();
        }
    }

    // Deallocate the allocated servlet instance
	//
    try {
        if (servlet != null) {
			//55555555555 釋放 sevlet 實例
            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)) {
			/ /666666666666     卸載wrapper
            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;

}

好了,我們終於看到了最終去處理requestresponse代碼的地方,雖然代碼很長,但是思路很清楚,大多數代碼都是在做檢測判斷等,invoke()方法我總結了核心是做了以下幾件事,我已經在代碼中注釋出來了:

  1. 加載最終處理請求requestservlet實例
  2. 創建過濾器鏈(filterChain)
  3. 調用過濾器鏈的doFilter方法來處理對應的requestresponse
  4. 后續處理釋放過濾器鏈
  5. 后續處理卸載該次處理的servlet實例
  6. 后續處理查看是否需要卸載對應的wrapper實例

個人總結出該方法做的比較重要的6件事,關於后續處理的部分我們就不查看了,有興趣的可以自行查看,我們主要看處理過程,也就是123三條。

加載對應的Servlet

對應的方法wrapper.allocate():

//代碼清單8
@Override
public Servlet allocate() throws ServletException {

    // If we are currently unloading this servlet, throw an exception
    if (unloading) {
        throw new ServletException(sm.getString("standardWrapper.unloading", getName()));
    }

    boolean newInstance = false;
    
    // If not SingleThreadedModel, return the same instance every time
	//111 判斷servlet是否是STM模式,如果是從來沒加載過的servlet 默認是非STM模式的
    if (!singleThreadModel) {
        // Load and initialize our instance if necessary
        if (instance == null || !instanceInitialized) {
            synchronized (this) {
                if (instance == null) {
                    try {
                        if (log.isDebugEnabled()) {
                            log.debug("Allocating non-STM instance");
                        }

                        // Note: We don't know if the Servlet implements
                        // SingleThreadModel until we have loaded it.
                        //22222222 加載servlet
                        instance = loadServlet();
                        newInstance = true;
                        if (!singleThreadModel) {
                            // For non-STM, increment here to prevent a race
                            // condition with unload. Bug 43683, test case
                            // #3
                            countAllocated.incrementAndGet();
                        }
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                    }
                }
				//3333 如果沒有初始化 初始化
                if (!instanceInitialized) {
                    initServlet(instance);
                }
            }
        }
		//44444 如果是STM模式的並且是分配的新對象 將該對象壓入pool中
		//之所以在 非STM模式的判斷里面又加入了STM模式判斷是因為
		// 沒有加載過的Servlet默認是非STM模式的,在loadServlet的時候回去判斷 該Servlet是否 是STM模式的
        if (singleThreadModel) {
            if (newInstance) {
                // Have to do this outside of the sync above to prevent a
                // possible deadlock
                synchronized (instancePool) {
                    instancePool.push(instance);
                    nInstances++;
                }
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("  Returning non-STM instance");
            }
            // For new instances, count will have been incremented at the
            // time of creation
            if (!newInstance) {
                countAllocated.incrementAndGet();
            }
            return instance;
        }
    }

    synchronized (instancePool) {
		//countAllocated 分配的活躍實例數量,對於一個非STM servlet 即使返回的是同一個數量,該字段也會增加
		//nInstances 分配的STM模式的servlet數量
		//maxInstances 可以分配的STM模式的servlet數量上限  默認是20
        while (countAllocated.get() >= nInstances) {
            // Allocate a new instance if possible, or else wait
            if (nInstances < maxInstances) {
                try {
                    instancePool.push(loadServlet());
                    nInstances++;
                } catch (ServletException e) {
                    throw e;
                } catch (Throwable e) {
                    ExceptionUtils.handleThrowable(e);
                    throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                }
            } else {
                try {
                    instancePool.wait();
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("  Returning allocated STM instance");
        }
        countAllocated.incrementAndGet();
        return instancePool.pop();
    }
}

在講解之前,我們先介紹個概念:STM

STM是SingleThreadModel類的縮寫,SingleThreadModel類是一個標志類(類似Serializable)。在Servlet2.4的規范中有說明:所有的servlet都可以實現該類,實現了該類的servlet不會同時有2個線程在調用同一個實例的service()方法。注意,這個意思並不是實現了SingleThreadModel類就代表該servlet線程安全。tomcat這樣處理主要是為了保證高性能而不是線程安全,真正的線程安全還是要service()方法中的代碼自己去控制。

我們繼續查看源碼,可以看到為了兼容STM和非STM模式servlet的分配allocate()方法寫的略顯復雜。總體是先判斷該servlt是否加載過,如果沒有加載過那么就是走標注1調用loadServlet()方法加載對應需要處理request的servlet。也許會奇怪為什么加載完了會再判斷該servlet是否是STM模式的,主要是因為在沒有加載過的servlet是無法判斷其是否是STM模式的,但是默認是非STM模式的,所以在加載完畢servlet以后需要再判斷一下是否是STM模式的然后作相應的處理。至於后面的synchronized代碼塊的處理我們先不看,我們先看下比較重要的標注2的地方的loadServlet()方法的源碼:

//代碼清單9
 /**
 * 加載一個servlet
 * @return
 * @throws ServletException
 */
public synchronized Servlet loadServlet() throws ServletException {

    //判斷servlet 狀態
    if (unloading) {
        throw new ServletException(
                sm.getString("standardWrapper.unloading", getName()));
    }

    // Nothing to do if we already have an instance or an instance pool
    //如果不是stm模式並且instance非空,那么直接返回instance(之前已經加載過該類)
    if (!singleThreadModel && (instance != null))
        return instance;
    //獲取輸出流,記日志
    PrintStream out = System.out;
    if (swallowOutput) {
        SystemLogHandler.startCapture();
    }

    Servlet servlet;
    try {
        long t1 = System.currentTimeMillis();
        // Complain if no servlet class has been specified
        //檢測
        if (servletClass == null) {
            unavailable(null);
            throw new ServletException
                (sm.getString("standardWrapper.notClass", getName()));
        }

        InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
        try {
            //111111新建實例
            servlet = (Servlet) instanceManager.newInstance(servletClass);
        } catch (ClassCastException e) {
           //異常處理 略
        } catch (Throwable e) {
          //異常處理 略
        }
        //處理servlet3.0 注解 MultipartConfig 配置該servlet的一些屬性(上傳文件的注解,上傳文件的一些屬性)
        if (multipartConfigElement == null) {
            MultipartConfig annotation =
                    servlet.getClass().getAnnotation(MultipartConfig.class);
            if (annotation != null) {
                multipartConfigElement =
                        new MultipartConfigElement(annotation);
            }
        }

        //處理 ServletSecurity 注解
        processServletSecurityAnnotation(servlet.getClass());

        // Special handling for ContainerServlet instances
        if ((servlet instanceof ContainerServlet) &&
                (isContainerProvidedServlet(servletClass) ||
                        ((Context) getParent()).getPrivileged() )) {
            ((ContainerServlet) servlet).setWrapper(this);
        }

        classLoadTime=(int) (System.currentTimeMillis() -t1);

        if (servlet instanceof SingleThreadModel) {
            //22222如果是STM模式,為了達到高性能 需要從緩存池中取對象 緩存池是個stack
            if (instancePool == null) {
                instancePool = new Stack<Servlet>();
            }
            singleThreadModel = true;
        }
        //333333初始化servlet  會調用自定義servlet的 init()方法
        initServlet(servlet);

        fireContainerEvent("load", this);

        loadTime=System.currentTimeMillis() -t1;
    } finally {
        if (swallowOutput) {
            String log = SystemLogHandler.stopCapture();
            if (log != null && log.length() > 0) {
                if (getServletContext() != null) {
                    getServletContext().log(log);
                } else {
                    out.println(log);
                }
            }
        }
    }
    return servlet;

}

loadServlet()方法也很簡單,主要就是標注123,標注1的地方是在新建servlet實例,標注2的地方是新建STM模式的servlet緩存池,標注3的地方是把新建的servlet實例初始化,值得注意的是在initServlet()方法里會調用servlet實例的init(),我們來查看下initServlet()方法:

//代碼清單10
 private synchronized void initServlet(Servlet servlet)
        throws ServletException {
    //已經初始化
    if (instanceInitialized && !singleThreadModel) return;

    // Call the initialization method of this servlet
    try {
        instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,
                                          servlet);

        if( Globals.IS_SECURITY_ENABLED) {
            boolean success = false;
            try {
                Object[] args = new Object[] { facade };
                SecurityUtil.doAsPrivilege("init",
                                           servlet,
                                           classType,
                                           args);
                success = true;
            } finally {
                if (!success) {
                    // destroy() will not be called, thus clear the reference now
                    SecurityUtil.remove(servlet);
                }
            }
        } else {
            //11111 servlet 初始化后 會調用一次  init()方法,可以自己復寫,也可以不復寫
            servlet.init(facade);
        }

        instanceInitialized = true;
        //觸發事件
        instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
                                          servlet);
    } catch (UnavailableException f) {
       //異常處理 略
    } catch (ServletException f) {
       //異常處理 略
    } catch (Throwable f) {
       //異常處理 略
    }
}

可以看到在標注1的地方調用了servlet實例的init()方法,其實這個就是用戶自定義servlet可以復寫也可以不復寫的init()方法,值得注意的是傳遞的對象StandardWrapperFacade的實例,這個類實現了ServletConfig類,同時包裝了StandardWrapper,我個人理解是這里傳遞StandardWrapperFacade對象主要目的是為了把StandardWrapper對servlet開發人員隱藏,不允許servlet開發人員隨意使用StandardWrapper,是為了安全着想。

說到這里我們看下代碼清單8的最后一段代碼

  //代碼清單11
  synchronized (instancePool) {
		//countAllocated 分配的活躍實例數量,對於一個非STM servlet 即使返回的是同一個數量,該字段也會增加
		//nInstances 分配的STM模式的servlet數量
		//maxInstances 可以分配的STM模式的servlet數量上限  默認是20
        while (countAllocated.get() >= nInstances) {
            // Allocate a new instance if possible, or else wait
            if (nInstances < maxInstances) {
                try {
                    instancePool.push(loadServlet());
                    nInstances++;
                } catch (ServletException e) {
                    throw e;
                } catch (Throwable e) {
                    ExceptionUtils.handleThrowable(e);
                    throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                }
            } else {
                try {
                    instancePool.wait();
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("  Returning allocated STM instance");
        }
        countAllocated.incrementAndGet();
        return instancePool.pop();
    }

在當前StandardWrapper分配的活躍實例數量大於STM的servlet分配的實例數量,並且分配的STM實例數量小於限定值(20)的時候會不停的實例化該STM模式的servlet並且塞到緩存池(instancePool)中。最后把緩存池中的棧頂對象彈出使用,也就是一開始實例化20個對象,每個請求彈出一個對象使用,這樣主要是為了保持高性能,以及每個請求使用一個servlet對象。

看到這里代碼清單7的servlet = wrapper.allocate()方法就看完了,主要作用是初始化需要被使用的servlet,我們繼續看代碼清單7的標注23的內容。

FilterChain的創建以及調用

//代碼清單12
//2222222 創建filterChain
ApplicationFilterFactory factory = ApplicationFilterFactory.getInstance();
ApplicationFilterChain filterChain = factory.createFilterChain(request, wrapper, servlet);
filterChain.doFilter(request.getRequest(), response.getResponse());

先查看createFilterChain()方法:

 //代碼清單13
public ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {

	//略

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

    filterChain.setServlet(servlet);

    filterChain.setSupport
        (((StandardWrapper)wrapper).getInstanceSupport());

    // Acquire the filter mappings for this Context
    StandardContext context = (StandardContext) wrapper.getParent();
	//22222 獲取所有的filter
    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
    String servletName = wrapper.getName();

    // Add the relevant path-mapped filters to this filter chain
	//33333333 添加匹配servlet路徑的filter
    for (int i = 0; i < filterMaps.length; i++) {
        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
            continue;
        }
        if (!matchFiltersURL(filterMaps[i], requestPath))
            continue;
		//44444444 獲取 filter對應的 ApplicationFilterConfig 對象
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            // FIXME - log configuration problem
            continue;
        }
        boolean isCometFilter = false;
        if (comet) {
            try {
                isCometFilter = filterConfig.getFilter() instanceof CometFilter;
            } catch (Exception e) {
                // Note: The try catch is there because getFilter has a lot of 
                // declared exceptions. However, the filter is allocated much
                // earlier
                Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(t);
            }
            if (isCometFilter) {
                filterChain.addFilter(filterConfig);
            }
        } else {
			// 5555555 添加filter
            filterChain.addFilter(filterConfig);
        }
    }

    // Add filters that match on servlet name second
	//666666666 添加匹配 servelt名字的filter
    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;
        }
        boolean isCometFilter = false;
        if (comet) {
            try {
                isCometFilter = filterConfig.getFilter() instanceof CometFilter;
            } catch (Exception e) {
                // Note: The try catch is there because getFilter has a lot of 
                // declared exceptions. However, the filter is allocated much
                // earlier
            }
            if (isCometFilter) {
                filterChain.addFilter(filterConfig);
            }
        } else {
            filterChain.addFilter(filterConfig);
        }
    }

    // Return the completed filter chain、
	//最終返回 filterchain
    return (filterChain);

}

代碼其實很簡單,注釋我都在代碼中添加了,先是創建ApplicationFilterChain實例,再向filterChain中添加和該servlet匹配的各種filter,主要這里需要解釋一下filter體系里幾個對象的關系。

  • FilterDef:代表一個filter,filter的定義類。類中的parameters變量存儲了在初始化過濾器的時候需要的所有參數,參數解析在解析web.xml的時候進行添加。
  • ApplicationFilterConfig:實現FilterConfig接口,用於管理web應用第一次啟動時創建的所有過濾器實例,簡單理解就是用來管理filter類的統一管理類。
  • ApplicationFilterChain:代表一個過濾器鏈實體,請求在到達對應servlet之前會先經過該實例擁有的所有filter。

除了filter相關知識以外,代碼清單13中context.findFilterMaps()表示了context對象和filter在啟動的時候已經被關聯在一起了,具體的關聯代碼前面說了一點,本文主要講解的是請求流程的處理,所以這里具體代碼就不查看了,只提一下。filter的初始化和關聯context的代碼都在context對象的初始化時進行,類似deploy項目一樣的監聽器HostConfig類,StandardContext類初始化的時候使用的監聽器是ContextConfig,具體代碼可以在該類中查找。

看完代碼清單13我們看到了ApplicationFilterChain的創建過程,從創建過程中我們知道了創建出來的filterChain實例擁有對於該請求應該應用的所有filter的實例引用。我們繼續查看doFilter()方法。

//代碼清單14
  @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) {
         	//異常處理略
        }
    } else {
        internalDoFilter(request,response);
    }
}

最后調用的是internalDoFilter()方法:

//代碼清單15

 /**
 * The int which is used to maintain the current position 
 * in the filter chain.
 * 當前正在調用的filter的編號
 */
private int pos = 0;
 /**
 * The int which gives the current number of filters in the chain.
 * filter的總的數量
 */
private int n = 0;


private void internalDoFilter(ServletRequest request, 
                              ServletResponse response)
    throws IOException, ServletException {

    // Call the next filter if there is one
    if (pos < n) {
		//1111 獲取ApplicationFilterConfig對象
        ApplicationFilterConfig filterConfig = filters[pos++];
        Filter filter = null;
        try {
			//2222222222222 獲取對應的filter實例
            filter = filterConfig.getFilter();
            support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
                                      filter, request, response);
            
            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 {
				//33333 調用該filter的`doFilter()`方法  
                filter.doFilter(request, response, this);
            }

            support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                                      filter, request, response);
        } catch (IOException e) {
           //異常處理略
        } catch (ServletException e) {
           //異常處理略
        } catch (RuntimeException e) {
           //異常處理略
        } catch (Throwable e) {
           //異常處理略
        }
        return;
    }

    // We fell off the end of the chain -- call the servlet instance
	// 所有的filter都調用完畢以后調用 對應的 servlet
    try {
        if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
            lastServicedRequest.set(request);
            lastServicedResponse.set(response);
        }

        support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
                                  servlet, request, response);
        if (request.isAsyncSupported()
                && !support.getWrapper().isAsyncSupported()) {
            request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                    Boolean.FALSE);
        }
        // Use potentially wrapped request from this point
        if ((request instanceof HttpServletRequest) &&
            (response instanceof HttpServletResponse)) {
                
            if( 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 {
				//444444 調用對應servlet的`service()`方法  
                servlet.service(request, response);
            }
        } else {
            servlet.service(request, response);
        }
        support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                                  servlet, request, response);
    } catch (IOException e) {
     //異常處理略
    } catch (ServletException e) {
       //異常處理略
    } catch (RuntimeException e) {
        //異常處理略
    } catch (Throwable e) {
       //異常處理略
    } finally {
        if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
            lastServicedRequest.set(null);
            lastServicedResponse.set(null);
        }
    }

}

從代碼清單15中我們可以看到,如果請求還在filter鏈中流轉那么就會一直調用filter.dofilter()方法,可以把代碼清單14和代碼清單15理解為一個遞歸方法,如果沒滿足pos < n這個條件就會一直調用filter.dofilter()方法,我們先看一下正常一個filter的dofilter()方法:

//代碼清單16
 @Override
public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {

	//自定義代碼略

  	chain.doFilter(request, response);
}

而在清單15標注3的地方傳遞的正是自身filterChain的實例,所以在filter中再調用chain.doFilter()方法,相當於又去調用代碼清單14的代碼了,這也是類似遞歸的地方。而pos < n這個條件表示的意思就是filter鏈中filter還沒有調用完畢。當filter調用完畢就會去調用請求對應的servlet的service方法。

看到這里我們終於把代碼清單7中提及的filterChain部分看完了,代碼清單7中后續的處理就不一一查看了,同時這個也是相當於整個處理流程的完結,因為已經調用到了對應servlet的service()方法。

既然到最后了,我們來總結下tomcat是如何處理HTTP請求的:

Socket-->Http11ConnectionHandler-->Http11Processor-->CoyoteAdapter-->StandardEngineValve-->StandardHostValve-->StandardContextValve-->ApplicationFilterChain-->Servlet

其實用uml畫個時序圖比較好,但是實在太懶了,大家可以隨便找個tomcat請求的時序圖配圖看文更清晰。

新年快樂(完)


免責聲明!

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



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