您的位置:首页 > 运维架构 > Tomcat

tomcat处理一个请求的过程

2017-07-16 22:32 369 查看
首先,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处理完了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  tomcat 处理请求