写点什么

Tomcat:网络请求原理分析

作者:IT巅峰技术
  • 2022 年 4 月 28 日
  • 本文字数:2946 字

    阅读完需:约 10 分钟

一、Http 请求过程总览

浏览器请求

http://localhost/test/index.jsp



  1. 用户点击网页内容,请求被发送到本机端口 8080,被在那里监听的 Coyote HTTP/1.1 Connector 获得。

  2. Connector 将 Request 包装成 ServletRequest 给它所在的 Service 的 Engine 来处理,并等待 Engine 的回应。

  3. Engine 获得请求 localhost/test/index.jsp,匹配所有的虚拟主机 Host。

  4. Engine 匹配到名为 localhost 的 Host(即使匹配不到也把请求交给该 Host 处理,因为该 Host 被定义为该 Engine 的默认主机),名为 localhost 的 Host 获得请求/test/index.jsp,匹配它所拥有的所有的 Context。Host 匹配到路径为/test 的 Context(如果匹配不到就把该请求交给路径名为""的 Context 去处理)。

  5. path="/test"的 Context 获得请求/index.jsp,在它的 mapping table 中寻找出对应的 Servlet。Context 匹配到 URL PATTERN 为*.jsp 的 Servlet,对应于 JspServlet 类。

  6. 构造 HttpServletRequest 对象和 HttpServletResponse 对象,作为参数调用 JspServlet 的 doGet()或 doPost().执行业务逻辑、数据存储等程序。

  7. Context 把执行完之后的 HttpServletResponse 对象返回给 Host。

  8. Host 把 HttpServletResponse 对象返回给 Engine。

  9. Engine 把 HttpServletResponse 对象返回 Connector,Connector 把 ServletResponse 对象封装成 Response。

  10. Connector 把 Response 返回给客户 Browser。

二、具体组件及源码分析

2.1 外部网络请求

2.1.1 Connector

用户在浏览器中输入一个 URL 地址之后浏览器将发起一个 Http 的请求,通过网络传输数据,请求到我们的 Tomcat 服务器中,Tomcat 使用 Connector 接收 Socket 请求数据并通过 Coyote 链接器封装底层网络通信,为 Catalina 容器提供了统一的接口。



在 Coyote 中,Tomcat 支持一下 3 种协议:


  1. HTTP/1.1 协议: 目前最常用的访问协议

  2. AJP 协议: Apache 提供的一种协议,用于和 Apache HTTP Server 集成。

  3. HTTP/2.0 协议: 下一代 HTTP 协议,自 Tomcat8.5 版本开始支持。


针对 HTTP 和 AJP 协议,Coyote 又按照 I/O 方式分别提供了不同的方案。


  1. BIO: 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成,Tomcat 早期网络请求的默认实现方式(自 8.5 版本开始,已经移除)

  2. NIO: 同步非阻塞的 I/O 模型(当前 Tomcat 的默认实现)

  3. NIO2: 异步非阻塞 I/O 模型

  4. APR: Apache 可移植运行库。

2.1.2 NioEndpoint

我们以 NioEndpoint 为例,来看一下 Tomcat 是如何实现网络请求收发的。



/*** Defaults to using HTTP/1.1 NIO implementation.* 默认使用NIO实现的http/1.1协议*/public Connector() { this("HTTP/1.1");}

public Connector(String protocol) { boolean apr = AprStatus.getUseAprConnector() && AprStatus.isInstanceCreated() && AprLifecycleListener.isAprAvailable(); ProtocolHandler p = null; try { //根据指定的协议,创建不同的处理器 p = ProtocolHandler.create(protocol, apr); } catch (Exception e) { log.error(sm.getString( "coyoteConnector.protocolHandlerInstantiationFailed"), e); } //省略部分代码...}
复制代码


我们发现 Tomcat 默认使用 HTTP/1.1 协议创建 Http11NioProtocol,它默认使用 NioEndpoint


public Http11NioProtocol() {    super(new NioEndpoint());}
复制代码


当 Connector 执行 init()的同时运行了 protocolHandler.init()最终运行了 NioEndpoint.init(),最终就是启动了一个 ServerSocket 并绑定监听端口。


protected void initServerSocket() throws Exception {  //省略部分代码  serverSock = ServerSocketChannel.open();    socketProperties.setProperties(serverSock.socket());    InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());    serverSock.bind(addr, getAcceptCount());}
复制代码


当 Connector 执行 start()方法时,最终执行了:NioEndpoint.startInternal()方法。该方法内部启动了一个 Poller 线程,一个 Acceptor 线程。其中,Acceptor 用于监听客户端连接,并将 Socket 连接放入待处理事件缓存池。Poller 循环从待处理的事件缓存队列中拿到请求交给线程池处理。Poller 将 Socket 请求包装成为:SocketProcessor 对象,执行 processor.process()方法,最终调用:getAdapter().service(request, response);这里的 getAdapter 即 CoyoteAdapter,最终找到容器中管道方法的第一个阀门方法,发起调用,此时,网络请求正式进入我们容器中。


// Calling the container 调用容器connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
复制代码


TIPS 如果想要详细了解 Tomcat 接收请求的流程图可以查看官网提供的资料


http://tomcat.apache.org/tomcat-9.0-doc/architecture/requestProcess/request-process.png

2.2 容器内部请求路径匹配

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)        throws Exception {        //将coyote链接器中的Request,Response转换为我们常用的HttpServletRequest,HttpServletResponse    Request request = (Request) req.getNote(ADAPTER_NOTES);    Response response = (Response) res.getNote(ADAPTER_NOTES);
//省略部分代码...
// 匹配请求路径,将当前request请求到匹配到的servlet上 postParseSuccess = postParseRequest(req, request, res, response); if (postParseSuccess) { //check valves if we support async request.setAsyncSupported( connector.getService().getContainer().getPipeline().isAsyncSupported()); // Calling the container 调用容器 connector.getService().getContainer().getPipeline().getFirst().invoke( request, response); // 省略部分代码...}
复制代码


在 Tomcat 启动的时候 Service 持有了一个 Mapper 对象,它借助 MapperListener 在应用启动的过程中,将 Engine,Host,Context,Wrapper 路径都搜集了起来。当一个请求过来的时候它会去当前 Service 中去匹配,最终将请求执行到我们 StandardWrapperValve。此时会创建 ApplicationFilterChain 过滤器链;通过调用过滤器内部方法 internalDoFilter;最终调用:javax.servlet.http.HttpServlet.service()方法,请求到应用中!

三、本文小结

我们通过一个 Http 请求路径分析了 Tomcat 是使用 Socket 接收网络请求,使用 Coyote 转换我们的网络协议参数,包装为 ServletRequest 和 ServletResponse,使用匹配请求路径的方式最终找到我们应用中的 Servlet 地址。至此,可以说我们对 Tomcat 的主要功能基本上已经了解的七七八八了,后续我们将会陆续的分享一些工作中常用的小技巧。




程序员的核心竞争力其实还是技术,因此对技术还是要不断的学习,关注 “IT 巅峰技术” 公众号 ,该公众号内容定位:中高级开发、架构师、中层管理人员等中高端岗位服务的,除了技术交流外还有很多架构思想和实战案例。


作者是 《 消息中间件 RocketMQ 技术内幕》 一书作者,同时也是 “RocketMQ 上海社区”联合创始人,曾就职于拼多多、德邦等公司,现任上市快递公司架构负责人,主要负责开发框架的搭建、中间件相关技术的二次开发和运维管理、混合云及基础服务平台的建设。

发布于: 刚刚阅读数: 2
用户头像

一线架构师、二线开发、三线管理 2021.12.07 加入

Redis6.X、ES7.X、Kafka3.X、RocketMQ5.0、Flink1.X、ClickHouse20.X、SpringCloud、Netty5等热门技术分享;架构设计方法论与实践;作者热销新书《RocketMQ技术内幕》;

评论

发布
暂无评论
Tomcat:网络请求原理分析_IT巅峰技术_InfoQ写作社区