freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Tomcat 处理 http 请求之源码分析 | 京东云技术团队
京东云技术团队 2023-05-29 14:07:56 120828
所属地 北京

本文将从请求获取与包装处理、请求传递给 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 作为监听者。他的生成图示:

1685340418_64744102be1d1af457e7a.png!small?1685340419340

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

3 Container 处理请求流程

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

1685340426_6474410a0f26b4459da98.png!small?1685340426646

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

作者:京东物流 毕会杰

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

# web安全 # 数据安全 # HTTP # tomcat # HTTP安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 京东云技术团队 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
京东云技术团队 LV.10
最懂产业的云
  • 1738 文章数
  • 91 关注者
高并发场景下的库存管理,理论与实战能否兼得?
2025-03-24
高并发场景下的库存管理,理论与实战能否兼得?
2025-03-24
引入JaCoCo导致的类型转换问题分析
2025-03-24