tomcat处理一个请求的过程

首先,tomcat是一个基于组件的服务器,它的构成组件都是可配置的,可以在tomcat目录下conf/server.xml中进行配置。其配置文件结构如下:

<Server>顶层类元素:一个配置文件中只能有一个<Server>元素,可包含多个Service。
    <Service>顶层类元素:本身不是容器,可包含一个Engine,多个Connector。
        <Connector/>连接器类元素:代表通信接口。
        <Engine>容器类元素:为特定的Service组件处理所有客户请求,可包含多个Host。engine为顶层Container
           <Host>容器类元素:为特定的虚拟主机处理所有客户请求,可包含多个Context。
              <Context>容器类元素:为特定的Web应用处理所有客户请求。代表一个应用,包含多个Wrapper(封装了servlet)
              </Context>
           </Host>
        </Engine>
     </Service>
</Server>

tomcat是基于组件的,分层避不可免,对于一个请求的处理,tomcat的链式调用比较长,让我们从接收请求开始说起。

tomcat处理socket请求的IO模型有BIO、NIO、AIO等,后两者IO模型比较复杂,而本文的主要关注点不在这,因此将tomcat配置成BIO模式。

tomcat接收请求的代码在Acceptor类中:

/**
 * The background thread that listens for incoming TCP/IP connections and
 * hands them off to an appropriate processor.
 * JIoEndpoint 的内部类
 */
protected class Acceptor extends AbstractEndpoint.Acceptor {
    @Override
    public void run() {
        // Loop until we receive a shutdown command
        while (running) {
            //if we have reached max connections, wait
            countUpOrAwaitConnection();

            Socket socket = null;
            //阻塞住 监听新的socket请求
            socket = serverSocketFactory.acceptSocket(serverSocket);

            // Configure the socket
            if (running && !paused && setSocketOptions(socket)) {
                // Hand this socket off to an appropriate processor 首先使用processSocket()简单处理socket
                if (!processSocket(socket)) {
                    countDownConnection();
                    // Close socket right away
                    closeSocket(socket);
                }
            } else {
                countDownConnection();
                // Close socket right away
                closeSocket(socket);
            }

        }
    }
}

Acceptor 继承了 AbstractEndpoint.Acceptor ,间接实现了Runnable接口,tomcat在运行时将Acceptor运行在一个后台线程内,单独监听socket请求,此线程的调用栈如下:

这里写图片描述

processSocket()方法如下:

/**
 * Process a new connection from a new client. Wraps the socket so
 * keep-alive and other attributes can be tracked and then passes the socket
 * to the executor for processing.
 */
protected boolean processSocket(Socket socket) {
    // Process the request from this socket
    SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
    wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
    wrapper.setSecure(isSSLEnabled());
    // During shutdown, executor may be null - avoid NPE
    if (!running) {
        return false;
    }
    getExecutor().execute(new SocketProcessor(wrapper));
    return true;
}

processSocket方法主要工作为将socket请求信息进行封装,然后将一个实现了Runnable接口的并包含socket信息的SocketProcessor对象交给线程池,进行执行,然后Acceptor线程从该方法返回,重新监听端口上的socket请求。

线程池receive到该worker后,取出一个线程处理socket请求,其调用栈如下:

这里写图片描述

调用栈底部的processor和handler主要处理TCP和HTTP协议的一些细节,CoyoteAdapter实现了对request输入流的编解码工作,并从service中获取顶层Container,将request和response交与容器组件,关键代码如下:

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

其中,在每一层容器中对请求的处理都运用了责任链模式,即所谓的Pipeline-Value处理模式,pipeline就像一个管道,表示对请求的链式处理过程,其中包含多个value,每个value代表对请求的一次处理,处理完成之后交给next Value进行处理,每层容器都至少有一个Value,这个Value被成为BaseValue,在pipeline中位于最后一个位置,比如Engine容器的BaseValue为StandardEngineValue。每一层的BaseValue会调用下一层容器的pipeline的第一个Value,由此形成一个长链:

这里写图片描述

到达pipeline长链最后一个value StandardWrapperValue后,会触发另外一个责任链模式:filterChain责任链,也就是我们平常熟悉的在web项目中配置的filter被调用的位置。

StandardWrapperValue调用filterChain代码如下:

//StandardWrapperValue类

public final void invoke(Request request, Response response){
    ...
    // Create the filter chain for this request
    ApplicationFilterFactory factory =
        ApplicationFilterFactory.getInstance();
    ApplicationFilterChain filterChain =
        factory.createFilterChain(request, wrapper, servlet);
    ...
    //开始调用filterChain
    filterChain.doFilter(request.getRequest(), response.getResponse());
    ...
}

ApplicationFilterFactory.getInstance()方法如下:

/**
 * Return the factory instance.
 */
//ApplicationFilterFactory类
public static ApplicationFilterFactory getInstance() {
    if (factory == null) {
        factory = new ApplicationFilterFactory();
    }
    return factory;
}

看到这个单例方法我有点蒙,这不明摆着存在竞态条件么,完全线程不安全的单例工厂 - - ,源码为tomcat7.0,难道这样没问题?

createFilterChain创建filterChain的代码如下:

//ApplicationFilterChain类

public ApplicationFilterChain createFilterChain
        (ServletRequest request, Wrapper wrapper, Servlet servlet) {
     ApplicationFilterChain filterChain = null;
     ...
     filterChain = new ApplicationFilterChain();
     ...
     filterChain.setServlet(servlet);
     ...
     // 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
    String servletName = wrapper.getName();

    // Add the relevant path-mapped filters to this filter chain 根据请求url找到对应的filter指针,添加进来
    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
    //如果配置了根据servlet名称过滤,则再寻找一遍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;
        }
        ...
        //addFilter时会对filter指针排重
        filterChain.addFilter(filterConfig);
    }

    // Return the completed filter chain
    return (filterChain);
}

创建filterChain代码比较简单,首先创建一个FilterChain对象,然后把url对应的servlet放进去,相当于pipeline-value模式中的最后一个BaseValue。之后从ServletContext中拿出所有的filter,然后根据url和servlet name找到符合条件的filter,根据顺序组装到filterChain中。

可能看到这里有些人会想,为什么要为每次请求都创建一个新的FilterChain对象呢,这显得有些不合常理,因为对于tomcat的大部分组件、filter、servlet都是单例的,而且频繁new操作有些消耗资源吧。确实是这样的,但这里将FilterChain做成prototype是有原因的,因为对于每个url,对应的filter个数都是不固定的,filterchain需要保存每个请求所对应的一个filter数组,以及调用到的filter的position,以便继续向下调用filter。这里的filter不能像pipeline-value模式那样组装起来,而是依靠filterChain来决定每个url的调用顺序。

创建完filterchain后,StandardWrapperValue就开始调用filterChain的doFilter方法了:

//ApplicationFilterChain类
public void doFilter(ServletRequest request, ServletResponse response){
...
    internalDoFilter(request,response);
...
}

private void internalDoFilter(ServletRequest request, 
                                  ServletResponse response){
    ...
    // Call the next filter if there is one
    if (pos < n) {
        ApplicationFilterConfig filterConfig = filters[pos++];
        Filter filter = null;
        filter = filterConfig.getFilter();
        filter.doFilter(request, response, this);
        return;
    }

    ...

    // We fell off the end of the chain -- call the servlet instance
    servlet.service(request, response);
    ...
}

pos为filterchain对象内的一个变量,用来标记当前调用的filter的位置,filter.doFilter(request, response, this),这句代码即调用咱们的filter了,记不记得咱们自己写filter时,如果不直接close outputstream,都会在doFilter最后写一句:filterChain.doFilter(req,res)来回调filterChain,让它继续调用接下来的filter。

在pos == n (filter数组长度)时,也就是filter调用完了,servlet.service(request, response)这一句便是调用我们的servlet了。我们的servlet在处理完请求后,再一步一步按原路返回,将处理结果通过socket写回客户端。

至此,一个请求就被tomcat处理完了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值