经过比较简单的步骤,终于把源码环境搭建好了,现在来简单的阅读下源码。
背景
我们知道,Tomcat就类似相当于一个Socket服务器,二浏览器就相当于一个客户端,浏览器请求链接,相当于发起了Socket请求,只不过是按HTTP协议来封装数据,然后Tomcat服务器接收到Socket连接后,解析数据然后返回。这里我就稍微看下源码Tomcat怎么做到的。
我在这篇文章
https://www.suibibk.com/topic/946547582480416768
中知道,Tomcat数据的流转大概是下面的流转模式模式
1、Tomcat中有一个叫做Endpoint的接口,抽象实现类是 AbstractEndpoint,具体子类在 NioEndpoint 和 Nio2Endpoint,其中两个重要组件:Acceptor 和 SocketProcessor。
2、其中Acceptor 用于监听 Socket 连接请求,SocketProcessor 用于处理收到的 Socket 请求,提交到线程池 Executor 处理。
3、然后Tomcat中还有一个Processor
- 接收 Endpoint 的 socket,读取字节流解析成 tomcat request 和 response,通过 adapter 将其提交到容器处理。Processor 的具体实现类 AjpProcessor、Http11Processor 实现了特定协议的解析方法和请求处理方式。
- 4、ProtocolHandler 接口负责解析请求生成 tomcat requst,CoyoteAdapter 的 service 方法,将 Tomcat Request 对象,转成 ServletRequest,再调用 service 方法。
如下图
5、然后数据就到了容器,容器通过 Pipeline-Valve 责任链,对请求一次处理,invoke 处理方法,每个容器都有一个 Pipeline,触发第一个 Valve,这个容器的 valve 都会被调到,不同容器之间通过 Pipeline 的 getBasic 方法,负责调用下层容器的第一个 Valve。
6.整个调用连由连接器中的 adapter 触发,调用 engine 中的第一个 Valve。
wrapper 容器的最后一个 valve 创建一个 filter 链,并调用 doFilter 方法,最终会调用到 servlet 的 service 方法。
如下图
也就是我们大概到看如下代码
- 1、NioEndpoint,里面应该有两个组件Acceptor和SocketProcessor
- 2、Http11Processor、CoyoteAdapter
- 3、各种valve(阀门),大概看了下源码目录发现在org.apache.catalina.core下面有StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve
- 4、还有我们是直接请求tomcat的8080,所以默认会用JSP的servlet:JspServlet
顺便说下,Engine下面有多个Host,也就是说有多个虚拟主机,可以配置多个域名,然后每个Host下面有多个Context,也就是一个域名下面有多个应用,然后Context下面有多个Wrapper,其实Wrapper就是包装了Servlet。
源码调试
那我们来调试代码看看是否是如此如此吧。
1、因为我们是用HTTP来请求的,所以我们先在NioEndpoint类来打断点,具体NioEndpoint的内部类Accept的run方法处打断点,按上面的步骤Acceptor 用于监听 Socket 连接请求,SocketProcessor 用于处理收到的 Socket 请求,提交到线程池 Executor 处理。
org.apache.tomcat.util.net.NioEndpoint 514行
protected class Acceptor extends AbstractEndpoint.Acceptor {
@Override
public void run() {
...
// Configure the socket
if (running && !paused) {
// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
if (!setSocketOptions(socket)) {
closeSocket(socket);
}
}
...
}
...
}
发现断点执行到了
setSocketOptions(socket)
然后代码走到了同一个类的
protected boolean setSocketOptions(SocketChannel socket) {
// Process the connection
try {
//disable blocking, APR style, we are gonna be polling it
socket.configureBlocking(false);
Socket sock = socket.socket();
socketProperties.setProperties(sock);
NioChannel channel = nioChannels.pop();
if (channel == null) {
SocketBufferHandler bufhandler = new SocketBufferHandler(
socketProperties.getAppReadBufSize(),
socketProperties.getAppWriteBufSize(),
socketProperties.getDirectBuffer());
if (isSSLEnabled()) {
channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);
} else {
channel = new NioChannel(socket, bufhandler);
}
} else {
channel.setIOChannel(socket);
channel.reset();
}
//关键代码
getPoller0().register(channel);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
try {
log.error("",t);
} catch (Throwable tt) {
ExceptionUtils.handleThrowable(tt);
}
// Tell to close the socket
return false;
}
return true;
}
方法,看上面的关键代码
getPoller0().register(channel);
这里的Poller我们点进去会发现实现了一个 Runnable接口,那肯定是跟线程有关的啦
public class Poller implements Runnable
这里说一句Poller也是NioEndpoint的内部类,而参数channel我们学过NIO的应该都知道,存放数据的的地方而已,具体不过多的论述。那调试发现下一步骤运行到Poller的run方法
@Override
public void run() {
...
if (socketWrapper != null) {
//在这里打断点
processKey(sk, socketWrapper);
}
...
}
到processKey方法
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
...
if (sk.isReadable()) {
if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
...
然后到:processSocket(attachment, SocketEvent.OPEN_READ, true)方法。
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
if (socketWrapper == null) {
return false;
}
SocketProcessorBase<S> sc = processorCache.pop();
if (sc == null) {
sc = createSocketProcessor(socketWrapper, event);
} else {
sc.reset(socketWrapper, event);
}
Executor executor = getExecutor();
if (dispatch && executor != null) {
//到这里断点,调试发现sc的对象
executor.execute(sc);
} else {
sc.run();
}
} catch (RejectedExecutionException ree) {
getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
getLog().error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
调试发现sc属于对象
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor
我们终于到了SocketProcessor类,也证明了说法Acceptor 用于监听 Socket 连接请求,SocketProcessor 用于处理收到的 Socket 请求,提交到线程池 Executor 处理的正确性
我们继续调试下去进入SocketProcessor的doRun方法,其实这个是父类的run方法里面调用duRun,所以线程最终会调用这个方法。
@Override
protected void doRun() {
...
if (event == null) {
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
//接着会执行这个方法
state = getHandler().process(socketWrapper, event);
}
...
}
这个getHandler().process方法属于AbstractProtocol的方法,继续调试进去
@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
...
if (processor == null) {
//这里会返回Http11Processor方法
processor = recycledProcessors.pop();
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("abstractConnectionHandler.processorPop", processor));
}
}
...
//执行这个方法
state = processor.process(wrapper, status);
...
}
会进到这个超长的方法,在这里
//这里会返回Http11Processor方法
processor = recycledProcessors.pop();
终于返回了我们步骤中的Http11Processor,也就是对应之前的图的Processor,也就是说准备读取字节流解析成 tomcat request 和 response,通过 adapter 将其提交到容器处理。Processor 的具体实现类 AjpProcessor、Http11Processor 实现了特定协议的解析方法和请求处理方式。
继续跟进进去
@Override
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
throws IOException {
...
state = service(socketWrapper);
...
}
然后继续就到了我们的Http11Processor#service方法.贼长
@Override
public SocketState service(SocketWrapperBase<?> socketWrapper)
throws IOException {
RequestInfo rp = request.getRequestProcessor();
rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
// Setting up the I/O
setSocketWrapper(socketWrapper);
// Flags
keepAlive = true;
openSocket = false;
readComplete = true;
boolean keptAlive = false;
SendfileState sendfileState = SendfileState.DONE;
while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
sendfileState == SendfileState.DONE && !endpoint.isPaused()) {
// Parsing the request header
try {
if (!inputBuffer.parseRequestLine(keptAlive)) {
if (inputBuffer.getParsingRequestLinePhase() == -1) {
return SocketState.UPGRADING;
} else if (handleIncompleteRequestLineRead()) {
break;
}
}
// Process the Protocol component of the request line
// Need to know if this is an HTTP 0.9 request before trying to
// parse headers.
//解析使用的协议,这里发现是HTTP1.1
prepareRequestProtocol();
...
//对请求进行解析
prepareRequest();
...
// 这里的getAdapter获取的就是CoyoteAdapter
getAdapter().service(request, response);
...
其中 prepareRequestProtocol();的作用的解析使用的协议
private void prepareRequestProtocol() {
MessageBytes protocolMB = request.protocol();
if (protocolMB.equals(Constants.HTTP_11)) {
http09 = false;
http11 = true;
protocolMB.setString(Constants.HTTP_11);
} else if (protocolMB.equals(Constants.HTTP_10)) {
http09 = false;
http11 = false;
keepAlive = false;
protocolMB.setString(Constants.HTTP_10);
} else if (protocolMB.equals("")) {
// HTTP/0.9
http09 = true;
http11 = false;
keepAlive = false;
} else {
// Unsupported protocol
http09 = false;
http11 = false;
// Send 505; Unsupported HTTP version
response.setStatus(505);
setErrorState(ErrorState.CLOSE_CLEAN, null);
if (log.isDebugEnabled()) {
log.debug(sm.getString("http11processor.request.prepare")+
" Unsupported HTTP version \""+protocolMB+"\"");
}
}
}
我们可以看到prepareRequest()方法
/**
* After reading the request headers, we have to setup the request filters.
*/
private void prepareRequest() throws IOException {
//设置http
if (endpoint.isSSLEnabled()) {
request.scheme().setString("https");
}
...
// Input filter setup
prepareInputFilters(headers);
//设置host和端口等
// Validate host name and extract port if present
parseHost(hostValueMB);
if (!getErrorState().isIoAllowed()) {
getAdapter().log(request, response, 0);
}
}
我们继续看代码发现
getAdapter().service(request, response);
这里的 getAdapter()就是CoyoteAdapter ,这样就跟之前的步骤对上了:Endpoint 接收到 socket 连接后,生成一个 socketProcessor 交给线程池处理,run 方法会调用 Processor 解析应用层协议,生成 tomcat request 后,调用 adapter 的 service 方法。那么接下来应该就要开始交给容器处理了。
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
//获取请求
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
...
try {
// Parse and set Catalina and configuration specific
// request parameters
//解析
postParseSuccess = postParseRequest(req, request, res, ...
//这里是关键代码
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
...
}
}
之前的步骤说整个容器的调用链由连接器中的 adapter 触发,调用 engine 中的第一个 Valve。就是下面的代码
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
- 上面的getService()是StandardService
- 上面的getContainer()是StandardEngine
- 上面的getPipeline()是StandardEngine的Pipeline,Pipeline里面有很多阀门(valve)。
- 上面的getFirst()是第一个阀门org.apache.catalina.core.StandardEngineValve[Catalina]
- 。
这样我们就成功进入到了invoke
final class StandardEngineValve extends ValveBase{
@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) {
// HTTP 0.9 or HTTP 1.0 request without a host when no default host
// is defined.
// Don't overwrite an existing error
if (!response.isError()) {
response.sendError(404);
}
return;
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(host.getPipeline().isAsyncSupported());
}
// 调用host的第一个阀门的invoke
host.getPipeline().getFirst().invoke(request, response);
}
继续走
// 调用host的第一个阀门的invoke
host.getPipeline().getFirst().invoke(request, response);
final class StandardHostValve extends ValveBase {
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Context to be used for this Request
//找到Context
Context context = request.getContext();
...
context.getPipeline().getFirst().invoke(request, response);
...
}
}
就跟图中描述的一样。继续
final class StandardContextValve extends ValveBase {
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Disallow any direct access to resources under WEB-INF or META-INF
//这里是/index.jsp
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(ContinueResponseTiming.IMMEDIATELY);
} 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
wrapper.getPipeline().getFirst().invoke(request, response);
}
}
这里到达最后一个valve
wrapper.getPipeline().getFirst().invoke(request, response);
final class StandardWrapperValve extends ValveBase {
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
...
try {
if (!unavailable) {
//wrapper是对servlet的包装
servlet = wrapper.allocate();
}
} catch (UnavailableException e) {
...
filterChain.doFilter
(request.getRequest(), response.getResponse());
...
}
}
}
servlet = wrapper.allocate();
返回的是
org.apache.jasper.servlet.JspServlet@69afd737
我们已经进行到了步骤中的最后:wrapper 容器的最后一个 valve 创建一个 filter 链,并调用 doFilter 方法,最终会调用到 servlet 的 service 方法。
上面的doFilter会进入
public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
...
internalDoFilter(request,response);
...
}
}
}
然后到 internalDoFilter(request,response);
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
...
servlet.service(request, response);
...
}
filter结束后终于到了
servlet.service(request, response);
这个servlet就是:org.apache.jasper.servlet.JspServlet
如果是我们自己写的servlet,那么调用的就是我们自己写的servlet的service方法
public class JspServlet extends HttpServlet implements PeriodicEventListener {
@Override
public void service (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// jspFile may be configured as an init-param for this servlet instance
String jspUri = jspFile;
if (jspUri == null) {
/*
* Check to see if the requested JSP has been the target of a
* RequestDispatcher.include()
*/
jspUri = (String) request.getAttribute(
RequestDispatcher.INCLUDE_SERVLET_PATH);
if (jspUri != null) {
/*
* Requested JSP has been target of
* RequestDispatcher.include(). Its path is assembled from the
* relevant javax.servlet.include.* request attributes
*/
String pathInfo = (String) request.getAttribute(
RequestDispatcher.INCLUDE_PATH_INFO);
if (pathInfo != null) {
jspUri += pathInfo;
}
} else {
/*
* Requested JSP has not been the target of a
* RequestDispatcher.include(). Reconstruct its path from the
* request's getServletPath() and getPathInfo()
*/
jspUri = request.getServletPath();
String pathInfo = request.getPathInfo();
if (pathInfo != null) {
jspUri += pathInfo;
}
}
}
if (log.isDebugEnabled()) {
log.debug("JspEngine --> " + jspUri);
log.debug("\t ServletPath: " + request.getServletPath());
log.debug("\t PathInfo: " + request.getPathInfo());
log.debug("\t RealPath: " + context.getRealPath(jspUri));
log.debug("\t RequestURI: " + request.getRequestURI());
log.debug("\t QueryString: " + request.getQueryString());
}
try {
boolean precompile = preCompile(request);
serviceJspFile(request, response, jspUri, precompile);
} catch (RuntimeException | IOException | ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException(e);
}
}
}
也就会返回一个流到浏览器这个客户端,页面就可以正常呈现了。
总结
果然根据前人总结出来的步骤看源码就是轻松,但是如果要自己从源码总结出这写步骤来,那就应该很困难了,不过相信肯定是对Tomcat有比较深入的了解了才回去看源码。
这一篇文章只是粗略的过了一遍一小部分代码,并没有过什么启动啊,socket啊等代码,如果是这些代码,那么得从启动类Bootstrap的main开始啦。
tomcat已经这么多年了,如果要做到全部源码都熟悉,那基本不可能,也没有必要,相关源码好好研究学习下就可以了。
以后再具体源码具体分析,比如session什么的。