Tomcat处理http请求之源码分析

tomcat,处理,http,请求,源码,分析 · 浏览次数 : 55

小编点评

**http 请求处理流程** 1. **请求包装处理 Tomcat 组件 Connector 的启动** - Connector 在启动的时候监听端口。 - 当接收到连接请求时,创建一个 Socket 和将其交给 SocketProcessor 线程处理。 2. **SocketProcessor 线程处理请求** - 通过 SocketWrapper 对 Socket 进行包装。 - 将包装好的 Socket交给 handler 处理。 3. **handler 处理请求** - 遍历请求的 stages 并设置相应的属性。 - 如果请求处于 OPEN_READ 状态,开始解析请求数据并设置 response 状态。 - 如果请求处于 CLOSE_CLEAN 状态,关闭连接。 4. **请求传递给 Container** - 在 request.setContext() 方法中设置 request 的上下文。 - 通过 URI 信息找到属于自己的 Context 和 Wrapper。 - 将 Context 和 Wrapper 设置到 request 中。 5. **Container 处理请求** - 在 request.setWrapper() 方法中设置 request 的 Wrapper。 - 通过 MapperListener 监听容器的变化并处理它们。 - 容器变化时,MapperListener 将所有容器添加到 mapper 中,以便 CoyoteAdapter 可以从中获取。 6. **CoyoteAdapter 处理请求** - 通过 MapperListener 找到匹配的 Context 和 Wrapper。 - 包装请求并将其交给 StandardEngineValve 进行处理。 - StandardEngineValve 将请求交给Servlet 处理完成。

正文

本文将从请求获取与包装处理、请求传递给Container、Container处理请求流程,这3部分来讲述一次http穿梭之旅。

1 请求包装处理

tomcat组件Connector在启动的时候会监听端口。以JIoEndpoint为例,在其Acceptor类中:

protected class Acceptor extends AbstractEndpoint.Acceptor {
    @Override
    public void run() {
        while (running) {
            ……
            try {
                //当前连接数
                countUpOrAwaitConnection();
                Socket socket = null;
                try {
                    //取出队列中的连接请求
                    socket = serverSocketFactory.acceptSocket(serverSocket);
                } catch (IOException ioe) {
                    countDownConnection();
                }
                if (running && !paused && setSocketOptions(socket)) {
                    //处理请求
                    if (!processSocket(socket)) {
                        countDownConnection();
                        closeSocket(socket);
                    }
                } else {
                    countDownConnection();
                    // Close socket right away
                    closeSocket(socket);
                }
            } 
            ……
        }
    }
}

在上面的代码中,socket = serverSocketFactory.acceptSocket(serverSocket);与客户端建立连接,将连接的socket交给processSocket(socket)来处理。在processSocket中,对socket进行包装一下交给线程池来处理:

protected boolean processSocket(Socket socket) {
    try {
        SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
        wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
        wrapper.setSecure(isSSLEnabled());
        //交给线程池处理连接
        getExecutor().execute(new SocketProcessor(wrapper));
    } 
    ……
    return true;
}

线程池处理的任务SocketProccessor,通过代码分析:

protected class SocketProcessor implements Runnable {
 
    protected SocketWrapper<Socket> socket = null;
    protected SocketStatus status = null;
 
    @Override
    public void run() {
        boolean launch = false;
        synchronized (socket) {
            SocketState state = SocketState.OPEN;
            try {
                serverSocketFactory.handshake(socket.getSocket());
            } 
            ……
            if ((state != SocketState.CLOSED)) {
                //委派给Handler来处理
                if (status == null) {
                    state = handler.process(socket, SocketStatus.OPEN_READ);
                } else {
                    state = handler.process(socket,status);
                }
            }}}
            ……
}

即在SocketProcessor中,将Socket交给handler处理,这个handler就是在Http11Protocol的构造方法中赋值的Http11ConnectionHandler,在该类的父类process方法中通过请求的状态,来创建Http11Processor处理器进行相应的处理,切到Http11Proccessor的父类AbstractHttp11Proccessor中。

public SocketState process(SocketWrapper socketWrapper) {
    RequestInfo rp = request.getRequestProcessor();
    rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
 
    // Setting up the I/O
    setSocketWrapper(socketWrapper);
    getInputBuffer().init(socketWrapper, endpoint);
    getOutputBuffer().init(socketWrapper, endpoint);
 
    while (!getErrorState().isError() && keepAlive && !comet && !isAsync() &&
            upgradeInbound == null &&
            httpUpgradeHandler == null && !endpoint.isPaused()) {
        ……
        if (!getErrorState().isError()) {
            // Setting up filters, and parse some request headers
            rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
            try {
                //请求预处理
                prepareRequest();
            } 
            ……
        }
        ……
        if (!getErrorState().isError()) {
            try {
                rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                //交由适配器处理
                adapter.service(request, response);
 
                if(keepAlive && !getErrorState().isError() && (
                        response.getErrorException() != null ||
                                (!isAsync() &&
                                statusDropsConnection(response.getStatus())))) {
                    setErrorState(ErrorState.CLOSE_CLEAN, null);
                }
                setCometTimeouts(socketWrapper);
            } 
        }
    }
    ……
}          

可以看到Request和Response的生成,从Socket中获取请求数据,keep-alive处理,数据包装等等信息,最后交给了CoyoteAdapter的service方法

2 请求传递给Container

在CoyoteAdapter的service方法中,主要有2个任务:

•第一个是org.apache.coyote.Request和
org.apache.coyote.Response到继承自HttpServletRequest的org.apache.catalina.connector.Request和org.apache.catalina.connector.Response转换,和Context,Wrapper定位。

•第二个是将请求交给StandardEngineValve处理。

public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res) {
    ……
    postParseSuccess = postParseRequest(req, request, res, response);
    ……
    connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
    ……
}

在postParseRequest方法中代码片段:

connector.getMapper().map(serverName, decodedURI, version,
                                      request.getMappingData());
request.setContext((Context) request.getMappingData().context);
request.setWrapper((Wrapper) request.getMappingData().wrapper);

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

public void startInternal() throws LifecycleException {
 
    setState(LifecycleState.STARTING);
    findDefaultHost();
 
    Engine engine = (Engine) connector.getService().getContainer();
    addListeners(engine);
 
    Container[] conHosts = engine.findChildren();
    for (Container conHost : conHosts) {
        Host host = (Host) conHost;
        if (!LifecycleState.NEW.equals(host.getState())) {
            registerHost(host);
        }
    }
}

MapperListener.startInternal()方法将所有Container容器信息保存到了mapper中。那么,现在初始化把所有容器都添加进去了,如果容器变化了将会怎么样?这就是上面所说的监听器的作用,容器变化了,MapperListener作为监听者。他的生成图示:

通过Mapper找到了该请求对应的Context和Wrapper后,CoyoteAdapter将包装好的请求交给Container处理。

3 Container处理请求流程

从下面的代码片段,我们很容易追踪整个Container的调用链: 用时序图画出来则是:

最终StandardWrapperValve将请求交给Servlet处理完成。至此一次http请求处理完毕。

作者:京东物流 毕会杰

内容来源:京东云开发者社区

与Tomcat处理http请求之源码分析相似的内容:

Tomcat处理http请求之源码分析

本文将从请求获取与包装处理、请求传递给Container、Container处理请求流程,这3部分来讲述一次http穿梭之旅。

[转帖]nginx的ip_hash算法

概念 根据用户请求的ip,利用算法映射成hash值,分配到特定的tomcat服务器中。主要是为了实现负载均衡,只要用户ip固定,则hash值固定,特定用户只能访问特定服务器,解决了session的问题。 源码分析 ip_hash算法的处理代码位于src\http\modules\ngx_http_u

[转帖]一次操作系统报错OutOfMemory Error的处理记录

在启动公司内嵌的tomcat容器时出现报错, 如下: # There is insufficient memory for the Java Runtime Environment to continue.# Native memory allocation (malloc) failed to a

[转帖]总结:nginx502:Tomcat调优之acceptCount

问题背景:UI页面点击会偶尔返回error,检查调用日志,发现nginx报502报错,因此本文即排查502报错原因。 如下红框可知,访问本机个备机的服务502了,用时3秒左右(可见并不是超时) 先给出原因:是因为tomcat8默认的acceptCount是100,请求量大的时候,会将一些来不及处理的

[转帖]Tomcat参数配置

前言 Tomcat是啥子,想必搜索tomcat配置的小伙伴应该无人不晓,无人不知了吧,但是我还是把官网看了一下有句话: Apache Tomcat software powers numerous large-scale, mission-critical web applications acro

[转帖]Tomcat 优雅关闭之路

本文首发于 vivo互联网技术 微信公众号链接:https://mp.weixin.qq.com/s/ZqkmoAR4JEYr0x0Suoq7QQ作者:马运杰 本文通过阅读Tomcat启动和关闭流程的源码,深入分析不同的Tomcat关闭方式背后的原理,让开发人员能够了解在使用不同的关闭方式时需要注意

[转帖]Tomcat maxKeepAliveRequests

https://www.cnblogs.com/turn2i/p/10480088.html 在写这个问题前,其实我是为了分析项目碰到的一个tcp close wait问题。这个问题就不在这里讲了。 造成的原因很简单,就是很多项目对httpclient的参数和使用都理解有问题,往往随便写一个或者使用

[转帖]Tomcat部署及优化

目录 一、Tomcat简介1 Tomcat的三大核心组件2 Java Servlet3 JSP全称Java Server Pages4 Tomcat 功能组件结构5 Tomcat 请求过程 二、Tomcat 服务部署1.关闭防火墙,将安装 Tomcat 所需软件包传到/opt目录下2.安装JDK3.

Tomcat--文件上传--文件包含--(CVE-2017-12615)&&(CVE-2020-1938)

Tomcat--文件上传--文件包含--(CVE-2017-12615)&&(CVE-2020-1938) 复现环境 采用Vulfocus靶场环境进行复现,搭建操作和文章参考具体搭建教程参考vulfocus不能同步的解决方法/vulfocus同步失败。 CVE-2017-12615 文件上传 漏洞简

Tomcat目录结构

Tomcat目录结构图如下: 1、bin目录 存放一些可执行的二进制文件,.sh 结尾的为linux下执行命令,.bat 结尾的为windows下执行命令。 catalina.sh:真正启动tomcat文件,可以在里面设置jvm参数。 startup.sh:启动tomcat(需事先配置好JAVA_H