首先,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处理完了。