tomcat源码分析-http请求在Container中的执行路线

简介:

  在CoyoteAdapter的service方法中,主要干了2件事:

    1. org.apache.coyote.Request -> org.apache.catalina.connector.Request extends HttpServletRequest

        org.apache.coyote.Response -> org.apache.catalina.connector. Response extends HttpServletResponse

        Context和Wrapper定位

    2. 将请求交给StandardEngineValue处理

 

Java代码   收藏代码
  1. public void service(org.apache.coyote.Request req,  
  2.                         org.apache.coyote.Response res) {  
  3.     //  
  4.     postParseSuccess = postParseRequest(req, request, res, response);  
  5.     //  
  6.     connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);  
  7.     //  
  8. }  

postParseRequest方法的代码片段

  

Java代码   收藏代码
  1. connector.getMapper().map(serverName, decodedURI, version,  
  2.                                       request.getMappingData());  
  3. request.setContext((Context) request.getMappingData().context);  
  4. request.setWrapper((Wrapper) request.getMappingData().wrapper);  

request通过URI的信息找到属于自己的Context和Wrapper。而这个Mapper保存了所有的容器信息,不记得的同学可以回到Connector的startInternal方法中,最有一行代码是mapperListener.start()。在MapperListener的start()方法中,

  

Java代码   收藏代码
  1. public void startInternal() throws LifecycleException {  
  2.   
  3.     setState(LifecycleState.STARTING);  
  4.     findDefaultHost();  
  5.   
  6.     Engine engine = (Engine) connector.getService().getContainer();  
  7.     addListeners(engine);  
  8.   
  9.     Container[] conHosts = engine.findChildren();  
  10.     for (Container conHost : conHosts) {  
  11.         Host host = (Host) conHost;  
  12.         if (!LifecycleState.NEW.equals(host.getState())) {  
  13.             registerHost(host);  
  14.         }  
  15.     }  
  16. }  

在容器初始化和变化时都会触发监听事件,从而将所有容器信息保存在Mapper中。之所以叫Mapper,因为它的主要作用就是定位Wrapper,而我们在web.xml里也配了filter/servlet-mapping。

 

    另外,由上面的代码可知,在随后的请求路线中,Engine可有Connector获取,Context和Wrapper可直接由Request获取,Host也可由Request获取。

Java代码   收藏代码
  1. public Host getHost() { return ((Host) mappingData.host); }  

 

    上面的代码中还涉及到了两个很重要的概念--Pipeline和Value,我们不妨先一睹Container的调用链和时序图。




 
     对于每个引入的http请求,连接器都会调用与其关联的servlet容器的invoke方法。然后,servlet容器会调用其所有子容器的invoke方法。为什么必须要有一个Host容器呢?

    在tomcat的实际部署中,若一个Context实例使用ContextConfig对象进行设置,就必须使用一个Host对象,原因如下:

    使用ContextConfig对象需要知道应用程序web.xml文件的位置,在其webConfig()方法中会解析web.xml文件

 

Java代码   收藏代码
  1. // Parse context level web.xml  
  2. InputSource contextWebXml = getContextWebXmlSource();  
  3. parseWebXml(contextWebXml, webXml, false);  

 

在getContextWebXmlSource方法里

Java代码   收藏代码
  1. // servletContext即core包下的ApplicationContext  
  2. url = servletContext.getResource(Constants.ApplicationWebXml);  

 

在getResource方法里

Java代码   收藏代码
  1. String hostName = context.getParent().getName();  

 因此,除非你自己实现一个ContextConfig类,否则,你必须使用一个Host容器。

 

    管道(Pipeline)包含该servlet容器将要调用的任务。一个阀(Value)表示一个具体的执行任务。在servlet容器的管道中,有一个基础阀,但是,可以添加任意数量的阀。阀的数量指的是额外添加的阀数量,即不包括基础阀。有意思的是,可以通过server.xml来动态添加阀。

    管道和阀的工作机制类似于servlet编程中的过滤器链和过滤器,tomcat的设计者采用的是链表数据结构来实现的链条机制,引入了一个类叫ValueContext。值得注意的是,基础阀总是最后执行。

    请求最终会被引导到StandardWrapper,本人也是首先从Wrapper这一层来入手Container的,直接看StandardWrapperValue的invoke方法

 

Java代码   收藏代码
  1. @Override  
  2. public final void invoke(Request request, Response response) {  
  3.     //  
  4.   
  5.     requestCount++;  
  6.     StandardWrapper wrapper = (StandardWrapper) getContainer();  
  7.     Servlet servlet = null;  
  8.     Context context = (Context) wrapper.getParent();  
  9.   
  10.     //  
  11.   
  12.     // Allocate a servlet instance to process this request  
  13.     try {  
  14.        if (!unavailable) {  
  15.           servlet = wrapper.allocate();  
  16.        }  
  17.     } catch (Exception e) {}  
  18.   
  19.     //  
  20.   
  21.     // Create the filter chain for this request  
  22.     ApplicationFilterFactory factory =  
  23.             ApplicationFilterFactory.getInstance();  
  24.     ApplicationFilterChain filterChain =  
  25.             factory.createFilterChain(request, wrapper, servlet);  
  26.   
  27.     //  
  28.   
  29.     // Call the filter chain for this request  
  30.     // NOTE: This also calls the servlet's service() method  
  31.     //  
  32.     filterChain.doFilter(request.getRequest(), response.getResponse());  
  33.     //  
  34.   
  35.     // Release the filter chain (if any) for this request  
  36.     if (filterChain != null) filterChain.release();  
  37.   
  38.     // Deallocate the allocated servlet instance  
  39.     if (servlet != null) wrapper.deallocate(servlet);  
  40.   
  41.     //  
  42. }  

 

上面代码中最重要的三处逻辑就是servlet实例的获取与卸载和filter链调用。我们先看卸载servlet实例的代码

Java代码   收藏代码
  1. @Override  
  2.     public void deallocate(Servlet servlet) throws ServletException {  
  3.   
  4.         // Unlock and free this instance  
  5.         synchronized (instancePool) {  
  6.             countAllocated.decrementAndGet();  
  7.             instancePool.push(servlet);  
  8.             instancePool.notify();  
  9.         }  
  10.   
  11.     }  

 

我们不考虑SingleThreadModel模型,因为较新版本的tomcat已经不用这种模型了(只有很老的版本才用),显然,通过上面的代码可以知道,常用的是线程池模型。下面给出加载servlet实例的代码

Java代码   收藏代码
  1. @Override  
  2. public Servlet allocate() throws ServletException {  
  3.       instance = loadServlet();  
  4.      
  5.       initServlet(instance);  
  6.   
  7.       synchronized (instancePool) {  
  8.   
  9.             while (countAllocated.get() >= nInstances) {  
  10.                 // Allocate a new instance if possible, or else wait  
  11.                 if (nInstances < maxInstances) {  
  12.                     try {  
  13.                         instancePool.push(loadServlet());  
  14.                         nInstances++;  
  15.                     } catch (Throwable e) {  
  16.                           
  17.                     }  
  18.                 } else {  
  19.                     try {  
  20.                         instancePool.wait();  
  21.                     } catch (InterruptedException e) {  
  22.                         // Ignore  
  23.                     }  
  24.                 }  
  25.             }  
  26.               
  27.             countAllocated.incrementAndGet();  
  28.             return instancePool.pop();  
  29.   
  30.         }  
  31. }  

 

最后,我们来看看filterChain的执行,

Java代码   收藏代码
  1. @Override  
  2.     public void doFilter(ServletRequest request, ServletResponse response)  
  3.         throws IOException, ServletException {  
  4.   
  5.     internalDoFilter(request,response);  
  6. }  
  7.   
  8. private void internalDoFilter(ServletRequest request,   
  9.                                   ServletResponse response)  
  10.         throws IOException, ServletException {  
  11.   
  12.     // Call the next filter if there is one  
  13.     if (pos < n) {  
  14.         filter.doFilter(request, response, this);  
  15.     }  
  16.   
  17.      // We fell off the end of the chain -- call the servlet instance  
  18.      servlet.service(request, response);  
  19. }  

 

显然,在调用web.xml里配的某个servlet时,都会先依次调用在web.xml里配的filter,这可谓是责任链设计模式的一种经典实现。

   

    好了,现在你可以把前文中Connector执行过程和本文的Container执行过程结合起来了。我始终相信,深入一点,你会更快乐。



原文链接:[http://wely.iteye.com/blog/2295240]

相关文章
|
4月前
|
JSON 监控 API
掌握使用 requests 库发送各种 HTTP 请求和处理 API 响应
本课程全面讲解了使用 Python 的 requests 库进行 API 请求与响应处理,内容涵盖环境搭建、GET 与 POST 请求、参数传递、错误处理、请求头设置及实战项目开发。通过实例教学,学员可掌握基础到高级技巧,并完成天气查询应用等实际项目,适合初学者快速上手网络编程与 API 调用。
544 130
|
7月前
|
JavaScript 前端开发 API
Node.js中发起HTTP请求的五种方式
以上五种方式,尽管只是冰山一角,但已经足以让编写Node.js HTTP请求的你,在连接世界的舞台上演奏出华丽的乐章。从原生的 `http`到现代的 `fetch`,每种方式都有独特的风格和表现力,让你的代码随着项目的节奏自由地舞动。
745 65
|
5月前
HTTP协议中请求方式GET 与 POST 什么区别 ?
GET和POST的主要区别在于参数传递方式、安全性和应用场景。GET通过URL传递参数,长度受限且安全性较低,适合获取数据;而POST通过请求体传递参数,安全性更高,适合提交数据。
608 2
|
6月前
|
Go 定位技术
Golang中设置HTTP请求代理的策略
在实际应用中,可能还需要处理代理服务器的连接稳定性、响应时间、以及错误处理等。因此,建议在使用代理时增加适当的错误重试机制,以确保网络请求的健壮性。此外,由于网络编程涉及的细节较多,彻底测试以确认代理配置符合预期的行为也是十分重要的。
292 8
|
6月前
|
缓存
|
5月前
|
JSON JavaScript API
Python模拟HTTP请求实现APP自动签到
Python模拟HTTP请求实现APP自动签到
|
5月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
6月前
|
缓存 JavaScript 前端开发
Vue 3 HTTP请求封装导致响应结果无法在浏览器中获取,尽管实际请求已成功。
通过逐项检查和调试,最终可以定位问题所在,修复后便能正常在浏览器中获取响应结果。
295 0
|
6月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
489 0
|
8月前
|
Go
在golang中发起http请求以获取访问域名的ip地址实例(使用net, httptrace库)
这只是追踪我们的行程的简单方法,不过希望你跟着探险家的脚步,即使是在互联网的隧道中,也可以找到你想去的地方。接下来就是你的探险之旅了,祝你好运!
474 26