我们平常编写好的HttpServlet类后,就可以处理请求了,但是服务器在接收到请求信息以后是如何将这些请求传递到我们编写的Servlet类中的???这个疑问在我心中的已经很久了,现在要来解决它。

  我们之前的一篇文章Tomcat中容器是什么以及容器与容器之间的数量关系。简单介绍Tomcat服务器的容器,以及容器与容器之间的关系。现在来讲一下在服务器端口接收到请求后,这些请求是如何在这些容器之间传递的。

一、先建立一个简单的动态WEB工程,然后写一个HttpServlet的实现类。代码如下

@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet
{
private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
System.out.println("这个doGet方法");
request.getRequestDispatcher("/test.jsp").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
} }

编写一个访问页面,index.jsp.

<body>
<h3>主页</h3>
<a href="/Test2/TestServlet" >Test</a>
</body>

一个响应页面。

<body>
<h3>成功响应</h3>
</body>

二、请求的需要经过哪些地方。

  请求信息会经过以下的接口(java类)及其子类来处理,就相当于要经过许多模块来处理。

三、具体步骤

  如上图,请求在服务器的处理经过:

①、AbstractEndpoint类及其子类来处理。

AbstractEndpoint这个抽象类中有一个抽象内部类Acceptor,这个Acceptor的实现类是AbstractEndpoint的三个子类的内部类Acceptor来实现的。

我们的请求就是被这Acceptor监听到并且接收的。这个类其实是一个线程类,因为AbstractEndpoint.Acceptor实现了Runnable接口。

      @Override
public void run() {
        int errorDelay = 0; // running表示endpoint的状态,在run()方法中这个while循环,会一直循环接收数据,直到endpoint 不处于运行状态。
while (running) { // 当endpoint暂停,但还是运行状态就让线程睡眠。
while (paused && running) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
          //endpoint不处于运行状态,就停止接收数据。
if (!running) {
break;
}
state = AcceptorState.RUNNING;
          
            
          //下面就是的接收数据的过程。
try {
//if we have reached max connections, wait
countUpOrAwaitConnection(); long socket = 0;
try {
// 从server sock 接收传进来的连接。
socket = Socket.accept(serverSock);
if (log.isDebugEnabled()) {
long sa = Address.get(Socket.APR_REMOTE, socket);
Sockaddr addr = Address.getInfo(sa);
log.debug(sm.getString("endpoint.apr.remoteport",
Long.valueOf(socket),
Long.valueOf(addr.port)));
}
} catch (Exception e) {
//省略了部分处理异常的代码。// The processor will recycle itself when it finishes
}
        
state = AcceptorState.ENDED;
}

以上就是AprEndpoint接收请求的过程。就是用一个接收器接收请求,过程中会使用套接字。但是好像并不是有的请求都会用这个Acceptor来接收。

当接收请求完毕,经过一系列的处理后就会由AprEndpoint的内部类SocketProcessor来将请求传给ProtocolHandler来处理。这个SocketProcessor也是一个线程类

它有一行代码将套接字传给了第二步来处理。

代码中的handler就是AbstractProtocol中的内部类AbstractConnectionHandler的实例,这样,套接字就被传到第②步了。

 

②、在AbstractConnectionHandler接收到第一步传来的套接字以后,对套接字进行处理,下面是它进行处理的代码。

        @SuppressWarnings("deprecation") // Old HTTP upgrade method has been deprecated
public SocketState process(SocketWrapper<S> wrapper,
SocketStatus status) {
      //省略部分代码         //这是第三步中Processor接口的实现类。connections是一个Map对象,套接字为键,Processor接口实现类实例为值。
Processor<S> processor = connections.get(socket);
if (status == SocketStatus.DISCONNECT && processor == null) {
// Nothing to do. Endpoint requested a close and there is no
// longer a processor associated with this socket.
return SocketState.CLOSED;
} wrapper.setAsync(false);
ContainerThreadMarker.markAsContainerThread(); try {
if (processor == null) {
processor = recycledProcessors.poll();
}
if (processor == null) {
processor = createProcessor();
} initSsl(wrapper, processor); SocketState state = SocketState.CLOSED;
do {
if (status == SocketStatus.DISCONNECT &&
!processor.isComet()) {
// Do nothing here, just wait for it to get recycled
// Don't do this for Comet we need to generate an end
// event (see BZ 54022)
} else if (processor.isAsync() || state == SocketState.ASYNC_END) {
state = processor.asyncDispatch(status);
if (state == SocketState.OPEN) {
// release() won't get called so in case this request
// takes a long time to process, remove the socket from
// the waiting requests now else the async timeout will
// fire
getProtocol().endpoint.removeWaitingRequest(wrapper);
// There may be pipe-lined data to read. If the data
// isn't processed now, execution will exit this
// loop and call release() which will recycle the
// processor (and input buffer) deleting any
// pipe-lined data. To avoid this, process it now.
                
                 //wapper就是套接字包装类的对象,这里还是理解为套接字,套接字在这里传给了第③步的Processor接口的实例。
state = processor.process(wrapper);
}
} else if (processor.isComet()) {
state = processor.event(status);
} else if (processor.getUpgradeInbound() != null) {
state = processor.upgradeDispatch();
} else if (processor.isUpgrade()) {
state = processor.upgradeDispatch(status);
} else {
              //注释同上,
state = processor.process(wrapper);
} if (state != SocketState.CLOSED && processor.isAsync()) {
state = processor.asyncPostProcess();
//省略掉部分代码
} catch(java.net.SocketException e) {
//将部分捕获异常的代码省略掉 // Make sure socket/processor is removed from the list of current
// connections
connections.remove(socket);
// Don't try to add upgrade processors back into the pool
if (!(processor instanceof org.apache.coyote.http11.upgrade.UpgradeProcessor)
&& !processor.isUpgrade()) {
release(wrapper, processor, true, false);
}
return SocketState.CLOSED;
}

③、第二步完成后,就会交给Processor<S>接口的实现类来处理。在AbstractHttp11Processor中的process(...)方法来处理.部分代码如下:

在这里将会创建请求和响应,但不是我们熟悉的HttpServletRequest或HttpServletResponse类型或其子类型。而是

org.apache.coyote.Request 和 org.apache.coyote.Response类型的,这个请求什么时候会变成的HttpServletRequest类型的呢??它将会在Connector中来进行处理,我说的是处理而不是类型转换是因为org.apache.coyote.Request 和HttpServletRequest并不是父子关系的类,总之,HttpServletRequest的请求是由Connector来创建,在CoyoteAdapter中处理成HttpServletRequest.
这个方法就是请求对象、响应对象和套接字进行信息交互的地方,也就是真真正正将套接字中的信息转化为请求信息,还要把响应信息写到套接字中。
由于只讨论请求的传递,所以我将方法的大部分代码删掉了。
    @Override
public SocketState process(SocketWrapper<S> socketWrapper)
throws IOException {       //省略部分代码
// Process the request in the adapter
if (!getErrorState().isError()) {
try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
            //请求和响应在这个类中被创建并将套接字中的信息写入的请求中。在这里,请求将会被传递到第④步。
            //这个adapter就是CoyoteAdapter的一个实例
            adapter.service(request, response);
// Handle when the response was committed before a serious
// error occurred. Throwing a ServletException should both
// set the status to 500 and set the errorException.
// If we fail here, then the response is likely already
// committed, so we can't try and set headers.
if(keepAlive && !getErrorState().isError() && (
response.getErrorException() != null ||
(!isAsync() &&
statusDropsConnection(response.getStatus())))) {
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
setCometTimeouts(socketWrapper);
} catch (InterruptedIOException e) {
//省略部分代码
}
}

④、第三步完成之后交给CoyoteAdapter来处理,CoyotoAdapter是将请求传入Server容器的切入点。

我一直不明白coyote是什么意思,有道上说是一种产于北美大草原的小狼。。。感觉不太能理解。所以我把Adapter接口的类注释贴上来,只有一句简单的话,如下:

Adapter. This represents the entry point in a coyote-based servlet container.

CoyoteAdapter中有一个service()方法。这个方法持有一个Connector的引用。这个Connector又持有一个Service容器的引用,而Service容器有持有一个Container(Container的实现类有StandardEngine、StandardHost等等)的引用。所以CoyoteAdapter就可以根据这些引用将请求传递到Server容器中了。如下图中的代码:

还是将CoyoteAdapter的service(...)方法的代码帖出来一下吧。

    /**
* Service method.
*/
@Override
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res)
throws Exception {
     //下面这两行代码就是将请求处理后转化为HttpServletRequest的
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
      //如果请求为null就让Connector来创建。
if (request == null) { // Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);
      //省略部分代码
} if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
} boolean comet = false;
boolean async = false;
boolean postParseSuccess = false; try {
// Parse and set Catalina and configuration specific
// request parameters
req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container 在这里将请求传到Server容器中。
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);   //省略部分代码 } } /**

⑤、如果上面的请求传递到的Container是StandaradEngine,那么就会Engine就会调用它持有的StandardPipeline对象来处理请求。StandardPipeline就相当于一条管道,这条管道中的有许多阀门,这些阀门会对请求进行处理,并且控制它下一步往哪里传递。StandardEngine的管道使用的阀门是StandardEngineValve。

⑥、和StandardEngine一样,StandardHost、StandardContext、StandardWrapper这几个容器都拥有自己的一条管道StandardPipeline来处理的请求。但是需要注意的是他们使用的阀门是不一样的。StandardHost则会使用StandardHostValve,其他的同理。。。。。。。

⑦、当最后一个StandardWrapperVale处理完请求后,此时这个请求会如何传递呢??此时请求已经到达了最底层的容器了。StandardWrapper就是最底层的容器,它不允许再有子容器。其实每一个StandardWrapper代表一个Servlet,因为每一个StandardWrapper都会持有一个Servlet实例的引用。阀门会将请求交给谁???看一张图:

这是debug的截图,右上图可知,当最后一个StandardWrapperValve处理完请求以后,把请求交给Filter来处理,此时请求进入过滤器链条中,也就是我们熟悉 filter chain。

⑧、当过滤器处理完之后当然是将请求传递给我们编写的HttpServlet来处理了。

以上就是请求如何,从在服务器的监听端口一步步经过处理和传递到我们自己编写的Servlet的过程。当然过程中有什么错误的地方还望各位大神指出。

本文为GooPolaris原创,转载须附上原文链接:https://i.cnblogs.com/EditPosts.aspx?postid=8115784

Tomcat是如何将请求一步步传递到我们编写的HttpServlet类中的的更多相关文章

  1. Nginx与Tomcat、Client之间请求的长连接配置不一致问题解决[转]

    http://bert82503.iteye.com/blog/2152613 前些天,线上出现“服务端长连接与客户端短连接引起Nginx的Writing.Active连接数过高问题”,这个是由于“服 ...

  2. Tomcat源码分析——请求原理分析(上)

    前言 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多人早期的J2EE项目,由程 ...

  3. Springmvc中 同步/异步请求参数的传递以及数据的返回

    转载:http://blog.csdn.net/qh_java/article/details/44802287 注意: 这里的返回就是返回到jsp页面 **** controller接收前台数据的方 ...

  4. tomcat的一次请求过程

    Tomcat处理一个HTTP请求的过程 假设来自客户的请求为: http://tomcat.com/yy/index.jsp 首先 dns 解析tomcat.com机器,一般是ng服务器ip地址 然后 ...

  5. springmvc中同步/异步请求参数的传递以及数据的返回

    注意: 这里的返回就是返回到jsp页面 **** controller接收前台数据的方式,以及将处理后的model 传向前台***** 1.前台传递数据的接受:传的属性名和javabean的属性相同 ...

  6. Tomcat源码分析——请求原理分析(下)

    前言 本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过<TOMCAT源码分析——请求原理分析(上)>和<TOMCAT源码分析——请求原理分析(中)>.在& ...

  7. Tomcat源码分析——请求原理分析(中)

    前言 在<TOMCAT源码分析——请求原理分析(上)>一文中已经介绍了关于Tomcat7.0处理请求前作的初始化和准备工作,请读者在阅读本文前确保掌握<TOMCAT源码分析——请求原 ...

  8. tomcat与jetty接收请求参数的区别

    [场景] 服务端点对点通知.A服务发起请求B服务,B同步返回接收成功:然后B开始处理逻辑:B处理完成后异步通知给A:A接收请求并处理,同步回写响应给B:完成. [先上代码] 服务端(接收端)代码: i ...

  9. Tomcat 详解URL请求

    这里分析一个实际的请求是如何在Tomcat中被处理的,以及最后是怎么样找到要处理的Servlet的?当我们在浏览器中输入http://hostname:port/contextPath/servlet ...

随机推荐

  1. Connection to https://dl-ssl.google.com refused的解决办法

    使用SDK Manager.exe安装Android开发环境的时候会遇到: Fetching https://dl-ssl.google.com/android/repository/addons_l ...

  2. audio标签的自动播放(ios)

    0.应用场景 前端移动端开发,经常有播放音乐的需求.比如我有公司做过类似支付宝的年度账单,功能是用户在查看年度账单的过程中播放轻音乐. 1.audio标签播放mp3 (一)常用属性和API介绍 1.c ...

  3. 浅谈.net MVC

    大学毕业对MVC的概念还不是很清晰,总觉得MVC是和三层一样的,是同一级别的架构.其实不然,三层架构是:BLL(业务逻辑层),DAL(数据库访问层),UI(页面显示层),而MVC仅仅是属于三层架构UI ...

  4. Html与Css关联到一起

    在HTML文件中使用Link标签连接独立的Css文件 将Link标签放在head标签中 like标签是空标签,只写开始标签,不写结束标签 我们需要为like标签设置一些属性 type的值设置为text ...

  5. 一键LNMP文件

    https://lnmp.org/ LNMP相关软件安装目录Nginx 目录: /usr/local/nginx/MySQL 目录 : /usr/local/mysql/MySQL数据库所在目录:/u ...

  6. jQuery的三种$()方式

    http://www.jb51.net/article/21660.htm   $号是jQuery“类”的一个别称,$()构造了一个jQuery对象.所以,“$()”可以叫做jQuery的构造函数(个 ...

  7. oAuth2.0及jwt介绍

    oAuth2.0流程示意如下: 关于jwt介绍: 说明: 关于jwt简单说明一下,jwt即为json web token,是用来和服务端建立加密通信所使用的的一种“约定”,主要组成见上图即可.服务端一 ...

  8. [javaSE] 多线程(守护线程)

    我们一般使用多线程,都是while的死循环,想要结束线程,只需退出死循环即可 当线程中调用了sleep()方法或者wait()方法,当前的线程就会进入冻结状态,这个线程就结束不了 调用Thread对象 ...

  9. 撩课-Python-每天5道面试题-第6天

    一. 函数的概念是什么? 在Python中如何定义?使用场景是什么? 概念 写了一段代码实现了某个小功能; 然后把这些代码集中到一块, 起一个名字; 下一次就可以根据这个名字再次使用这个代码块, 这就 ...

  10. 撩课-Java每天5道面试题第9天

    撩课Java+系统架构 视频 点击开始学习 76.XML技术的作用? XML技术用于数据存储. 信息配置. 数据交换三方面. 可以将数据存储在XML中, 通过节点. 元素内容. 属性标示数据内容及关系 ...