个人随笔
目录
Tomcat篇:Tomcat源码阅读初体验(三)
2022-02-26 19:22:28

经过比较简单的步骤,终于把源码环境搭建好了,现在来简单的阅读下源码。

背景

我们知道,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行

  1. protected class Acceptor extends AbstractEndpoint.Acceptor {
  2. @Override
  3. public void run() {
  4. ...
  5. // Configure the socket
  6. if (running && !paused) {
  7. // setSocketOptions() will hand the socket off to
  8. // an appropriate processor if successful
  9. if (!setSocketOptions(socket)) {
  10. closeSocket(socket);
  11. }
  12. }
  13. ...
  14. }
  15. ...
  16. }

发现断点执行到了

  1. setSocketOptions(socket)

然后代码走到了同一个类的

  1. protected boolean setSocketOptions(SocketChannel socket) {
  2. // Process the connection
  3. try {
  4. //disable blocking, APR style, we are gonna be polling it
  5. socket.configureBlocking(false);
  6. Socket sock = socket.socket();
  7. socketProperties.setProperties(sock);
  8. NioChannel channel = nioChannels.pop();
  9. if (channel == null) {
  10. SocketBufferHandler bufhandler = new SocketBufferHandler(
  11. socketProperties.getAppReadBufSize(),
  12. socketProperties.getAppWriteBufSize(),
  13. socketProperties.getDirectBuffer());
  14. if (isSSLEnabled()) {
  15. channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);
  16. } else {
  17. channel = new NioChannel(socket, bufhandler);
  18. }
  19. } else {
  20. channel.setIOChannel(socket);
  21. channel.reset();
  22. }
  23. //关键代码
  24. getPoller0().register(channel);
  25. } catch (Throwable t) {
  26. ExceptionUtils.handleThrowable(t);
  27. try {
  28. log.error("",t);
  29. } catch (Throwable tt) {
  30. ExceptionUtils.handleThrowable(tt);
  31. }
  32. // Tell to close the socket
  33. return false;
  34. }
  35. return true;
  36. }

方法,看上面的关键代码

  1. getPoller0().register(channel);

这里的Poller我们点进去会发现实现了一个 Runnable接口,那肯定是跟线程有关的啦

  1. public class Poller implements Runnable

这里说一句Poller也是NioEndpoint的内部类,而参数channel我们学过NIO的应该都知道,存放数据的的地方而已,具体不过多的论述。那调试发现下一步骤运行到Poller的run方法

  1. @Override
  2. public void run() {
  3. ...
  4. if (socketWrapper != null) {
  5. //在这里打断点
  6. processKey(sk, socketWrapper);
  7. }
  8. ...
  9. }

到processKey方法

  1. protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
  2. ...
  3. if (sk.isReadable()) {
  4. if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
  5. closeSocket = true;
  6. }
  7. }
  8. ...

然后到:processSocket(attachment, SocketEvent.OPEN_READ, true)方法。

  1. public boolean processSocket(SocketWrapperBase<S> socketWrapper,
  2. SocketEvent event, boolean dispatch) {
  3. try {
  4. if (socketWrapper == null) {
  5. return false;
  6. }
  7. SocketProcessorBase<S> sc = processorCache.pop();
  8. if (sc == null) {
  9. sc = createSocketProcessor(socketWrapper, event);
  10. } else {
  11. sc.reset(socketWrapper, event);
  12. }
  13. Executor executor = getExecutor();
  14. if (dispatch && executor != null) {
  15. //到这里断点,调试发现sc的对象
  16. executor.execute(sc);
  17. } else {
  18. sc.run();
  19. }
  20. } catch (RejectedExecutionException ree) {
  21. getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
  22. return false;
  23. } catch (Throwable t) {
  24. ExceptionUtils.handleThrowable(t);
  25. // This means we got an OOM or similar creating a thread, or that
  26. // the pool and its queue are full
  27. getLog().error(sm.getString("endpoint.process.fail"), t);
  28. return false;
  29. }
  30. return true;
  31. }

调试发现sc属于对象

  1. org.apache.tomcat.util.net.NioEndpoint$SocketProcessor

我们终于到了SocketProcessor类,也证明了说法Acceptor 用于监听 Socket 连接请求,SocketProcessor 用于处理收到的 Socket 请求,提交到线程池 Executor 处理的正确性

我们继续调试下去进入SocketProcessor的doRun方法,其实这个是父类的run方法里面调用duRun,所以线程最终会调用这个方法。

  1. @Override
  2. protected void doRun() {
  3. ...
  4. if (event == null) {
  5. state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
  6. } else {
  7. //接着会执行这个方法
  8. state = getHandler().process(socketWrapper, event);
  9. }
  10. ...
  11. }

这个getHandler().process方法属于AbstractProtocol的方法,继续调试进去

  1. @Override
  2. public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
  3. ...
  4. if (processor == null) {
  5. //这里会返回Http11Processor方法
  6. processor = recycledProcessors.pop();
  7. if (getLog().isDebugEnabled()) {
  8. getLog().debug(sm.getString("abstractConnectionHandler.processorPop", processor));
  9. }
  10. }
  11. ...
  12. //执行这个方法
  13. state = processor.process(wrapper, status);
  14. ...
  15. }

会进到这个超长的方法,在这里

  1. //这里会返回Http11Processor方法
  2. processor = recycledProcessors.pop();

终于返回了我们步骤中的Http11Processor,也就是对应之前的图的Processor,也就是说准备读取字节流解析成 tomcat request 和 response,通过 adapter 将其提交到容器处理。Processor 的具体实现类 AjpProcessor、Http11Processor 实现了特定协议的解析方法和请求处理方式。
继续跟进进去

  1. @Override
  2. public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
  3. throws IOException {
  4. ...
  5. state = service(socketWrapper);
  6. ...
  7. }

然后继续就到了我们的Http11Processor#service方法.贼长

  1. @Override
  2. public SocketState service(SocketWrapperBase<?> socketWrapper)
  3. throws IOException {
  4. RequestInfo rp = request.getRequestProcessor();
  5. rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
  6. // Setting up the I/O
  7. setSocketWrapper(socketWrapper);
  8. // Flags
  9. keepAlive = true;
  10. openSocket = false;
  11. readComplete = true;
  12. boolean keptAlive = false;
  13. SendfileState sendfileState = SendfileState.DONE;
  14. while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
  15. sendfileState == SendfileState.DONE && !endpoint.isPaused()) {
  16. // Parsing the request header
  17. try {
  18. if (!inputBuffer.parseRequestLine(keptAlive)) {
  19. if (inputBuffer.getParsingRequestLinePhase() == -1) {
  20. return SocketState.UPGRADING;
  21. } else if (handleIncompleteRequestLineRead()) {
  22. break;
  23. }
  24. }
  25. // Process the Protocol component of the request line
  26. // Need to know if this is an HTTP 0.9 request before trying to
  27. // parse headers.
  28. //解析使用的协议,这里发现是HTTP1.1
  29. prepareRequestProtocol();
  30. ...
  31. //对请求进行解析
  32. prepareRequest();
  33. ...
  34. // 这里的getAdapter获取的就是CoyoteAdapter
  35. getAdapter().service(request, response);
  36. ...

其中 prepareRequestProtocol();的作用的解析使用的协议

  1. private void prepareRequestProtocol() {
  2. MessageBytes protocolMB = request.protocol();
  3. if (protocolMB.equals(Constants.HTTP_11)) {
  4. http09 = false;
  5. http11 = true;
  6. protocolMB.setString(Constants.HTTP_11);
  7. } else if (protocolMB.equals(Constants.HTTP_10)) {
  8. http09 = false;
  9. http11 = false;
  10. keepAlive = false;
  11. protocolMB.setString(Constants.HTTP_10);
  12. } else if (protocolMB.equals("")) {
  13. // HTTP/0.9
  14. http09 = true;
  15. http11 = false;
  16. keepAlive = false;
  17. } else {
  18. // Unsupported protocol
  19. http09 = false;
  20. http11 = false;
  21. // Send 505; Unsupported HTTP version
  22. response.setStatus(505);
  23. setErrorState(ErrorState.CLOSE_CLEAN, null);
  24. if (log.isDebugEnabled()) {
  25. log.debug(sm.getString("http11processor.request.prepare")+
  26. " Unsupported HTTP version \""+protocolMB+"\"");
  27. }
  28. }
  29. }

我们可以看到prepareRequest()方法

  1. /**
  2. * After reading the request headers, we have to setup the request filters.
  3. */
  4. private void prepareRequest() throws IOException {
  5. //设置http
  6. if (endpoint.isSSLEnabled()) {
  7. request.scheme().setString("https");
  8. }
  9. ...
  10. // Input filter setup
  11. prepareInputFilters(headers);
  12. //设置host和端口等
  13. // Validate host name and extract port if present
  14. parseHost(hostValueMB);
  15. if (!getErrorState().isIoAllowed()) {
  16. getAdapter().log(request, response, 0);
  17. }
  18. }

我们继续看代码发现

  1. getAdapter().service(request, response);

这里的 getAdapter()就是CoyoteAdapter ,这样就跟之前的步骤对上了:Endpoint 接收到 socket 连接后,生成一个 socketProcessor 交给线程池处理,run 方法会调用 Processor 解析应用层协议,生成 tomcat request 后,调用 adapter 的 service 方法。那么接下来应该就要开始交给容器处理了。

  1. @Override
  2. public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
  3. throws Exception {
  4. //获取请求
  5. Request request = (Request) req.getNote(ADAPTER_NOTES);
  6. Response response = (Response) res.getNote(ADAPTER_NOTES);
  7. ...
  8. try {
  9. // Parse and set Catalina and configuration specific
  10. // request parameters
  11. //解析
  12. postParseSuccess = postParseRequest(req, request, res, ...
  13. //这里是关键代码
  14. connector.getService().getContainer().getPipeline().getFirst().invoke(
  15. request, response);
  16. }
  17. ...
  18. }
  19. }

之前的步骤说整个容器的调用链由连接器中的 adapter 触发,调用 engine 中的第一个 Valve。就是下面的代码

  1. 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
  1. final class StandardEngineValve extends ValveBase{
  2. @Override
  3. public final void invoke(Request request, Response response)
  4. throws IOException, ServletException {
  5. // Select the Host to be used for this Request
  6. //获取虚拟主机
  7. Host host = request.getHost();
  8. if (host == null) {
  9. // HTTP 0.9 or HTTP 1.0 request without a host when no default host
  10. // is defined.
  11. // Don't overwrite an existing error
  12. if (!response.isError()) {
  13. response.sendError(404);
  14. }
  15. return;
  16. }
  17. if (request.isAsyncSupported()) {
  18. request.setAsyncSupported(host.getPipeline().isAsyncSupported());
  19. }
  20. // 调用host的第一个阀门的invoke
  21. host.getPipeline().getFirst().invoke(request, response);
  22. }

继续走

  1. // 调用host的第一个阀门的invoke
  2. host.getPipeline().getFirst().invoke(request, response);
  1. final class StandardHostValve extends ValveBase {
  2. @Override
  3. public final void invoke(Request request, Response response)
  4. throws IOException, ServletException {
  5. // Select the Context to be used for this Request
  6. //找到Context
  7. Context context = request.getContext();
  8. ...
  9. context.getPipeline().getFirst().invoke(request, response);
  10. ...
  11. }
  12. }

就跟图中描述的一样。继续

  1. final class StandardContextValve extends ValveBase {
  2. @Override
  3. public final void invoke(Request request, Response response)
  4. throws IOException, ServletException {
  5. // Disallow any direct access to resources under WEB-INF or META-INF
  6. //这里是/index.jsp
  7. MessageBytes requestPathMB = request.getRequestPathMB();
  8. if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
  9. || (requestPathMB.equalsIgnoreCase("/META-INF"))
  10. || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
  11. || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
  12. response.sendError(HttpServletResponse.SC_NOT_FOUND);
  13. return;
  14. }
  15. // Select the Wrapper to be used for this Request
  16. Wrapper wrapper = request.getWrapper();
  17. if (wrapper == null || wrapper.isUnavailable()) {
  18. response.sendError(HttpServletResponse.SC_NOT_FOUND);
  19. return;
  20. }
  21. // Acknowledge the request
  22. try {
  23. response.sendAcknowledgement(ContinueResponseTiming.IMMEDIATELY);
  24. } catch (IOException ioe) {
  25. container.getLogger().error(sm.getString(
  26. "standardContextValve.acknowledgeException"), ioe);
  27. request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
  28. response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
  29. return;
  30. }
  31. if (request.isAsyncSupported()) {
  32. request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
  33. }
  34. //找Wrapper
  35. wrapper.getPipeline().getFirst().invoke(request, response);
  36. }
  37. }

这里到达最后一个valve

  1. wrapper.getPipeline().getFirst().invoke(request, response);
  1. final class StandardWrapperValve extends ValveBase {
  2. @Override
  3. public final void invoke(Request request, Response response)
  4. throws IOException, ServletException {
  5. ...
  6. try {
  7. if (!unavailable) {
  8. //wrapper是对servlet的包装
  9. servlet = wrapper.allocate();
  10. }
  11. } catch (UnavailableException e) {
  12. ...
  13. filterChain.doFilter
  14. (request.getRequest(), response.getResponse());
  15. ...
  16. }
  17. }
  18. }
  1. servlet = wrapper.allocate();

返回的是

  1. org.apache.jasper.servlet.JspServlet@69afd737

我们已经进行到了步骤中的最后:wrapper 容器的最后一个 valve 创建一个 filter 链,并调用 doFilter 方法,最终会调用到 servlet 的 service 方法。

上面的doFilter会进入

  1. public final class ApplicationFilterChain implements FilterChain {
  2. @Override
  3. public void doFilter(ServletRequest request, ServletResponse response)
  4. throws IOException, ServletException {
  5. ...
  6. internalDoFilter(request,response);
  7. ...
  8. }
  9. }
  10. }

然后到 internalDoFilter(request,response);

  1. private void internalDoFilter(ServletRequest request,
  2. ServletResponse response)
  3. throws IOException, ServletException {
  4. ...
  5. servlet.service(request, response);
  6. ...
  7. }

filter结束后终于到了

  1. servlet.service(request, response);

这个servlet就是:org.apache.jasper.servlet.JspServlet

如果是我们自己写的servlet,那么调用的就是我们自己写的servlet的service方法

  1. public class JspServlet extends HttpServlet implements PeriodicEventListener {
  2. @Override
  3. public void service (HttpServletRequest request, HttpServletResponse response)
  4. throws ServletException, IOException {
  5. // jspFile may be configured as an init-param for this servlet instance
  6. String jspUri = jspFile;
  7. if (jspUri == null) {
  8. /*
  9. * Check to see if the requested JSP has been the target of a
  10. * RequestDispatcher.include()
  11. */
  12. jspUri = (String) request.getAttribute(
  13. RequestDispatcher.INCLUDE_SERVLET_PATH);
  14. if (jspUri != null) {
  15. /*
  16. * Requested JSP has been target of
  17. * RequestDispatcher.include(). Its path is assembled from the
  18. * relevant javax.servlet.include.* request attributes
  19. */
  20. String pathInfo = (String) request.getAttribute(
  21. RequestDispatcher.INCLUDE_PATH_INFO);
  22. if (pathInfo != null) {
  23. jspUri += pathInfo;
  24. }
  25. } else {
  26. /*
  27. * Requested JSP has not been the target of a
  28. * RequestDispatcher.include(). Reconstruct its path from the
  29. * request's getServletPath() and getPathInfo()
  30. */
  31. jspUri = request.getServletPath();
  32. String pathInfo = request.getPathInfo();
  33. if (pathInfo != null) {
  34. jspUri += pathInfo;
  35. }
  36. }
  37. }
  38. if (log.isDebugEnabled()) {
  39. log.debug("JspEngine --> " + jspUri);
  40. log.debug("\t ServletPath: " + request.getServletPath());
  41. log.debug("\t PathInfo: " + request.getPathInfo());
  42. log.debug("\t RealPath: " + context.getRealPath(jspUri));
  43. log.debug("\t RequestURI: " + request.getRequestURI());
  44. log.debug("\t QueryString: " + request.getQueryString());
  45. }
  46. try {
  47. boolean precompile = preCompile(request);
  48. serviceJspFile(request, response, jspUri, precompile);
  49. } catch (RuntimeException | IOException | ServletException e) {
  50. throw e;
  51. } catch (Throwable e) {
  52. ExceptionUtils.handleThrowable(e);
  53. throw new ServletException(e);
  54. }
  55. }
  56. }

也就会返回一个流到浏览器这个客户端,页面就可以正常呈现了。

总结

果然根据前人总结出来的步骤看源码就是轻松,但是如果要自己从源码总结出这写步骤来,那就应该很困难了,不过相信肯定是对Tomcat有比较深入的了解了才回去看源码。

这一篇文章只是粗略的过了一遍一小部分代码,并没有过什么启动啊,socket啊等代码,如果是这些代码,那么得从启动类Bootstrap的main开始啦。

tomcat已经这么多年了,如果要做到全部源码都熟悉,那基本不可能,也没有必要,相关源码好好研究学习下就可以了。

以后再具体源码具体分析,比如session什么的。

 185

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号