Request 跨线程访问问题

news2024/9/20 10:00:06

优质博文:IT-BLOG-CN

此篇文章是基于 Tomcat Request Cookie 丢失问题 文章的一个延续

一、Request 跨线程访问问题

问题代码摘要

为了方便选择发起get请求,然后只需要传递一个参数就行,核心步骤是要把request传递到异步线程里面去,调用getParameter再次获取对应入参。

@GetMapping("/getTest")
public String getTest(HttpServletRequest request) {
    String age = request.getParameter("age");
    System.out.println("age=" + age);
    new Thread(() -> {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        String age1 = request.getParameter("age");
        System.out.println("age1=" + age1);
    }).start();
    return "success";
}

获取请求:http://127.0.0.1:8080/getTest?age=18

从控制台你可以看到这样的输出:

age=18
age1=null

当再次发起调用,会看到控制台的输出是这样的:

age=18
age1=null
age=null
age1=null

和上面的问题类似,这里也有一个类似的方法:getParameter

@Override
public String getParameter(String name) {
    if (!parametersParsed) {
        parseParameters();
    }
    return coyoteRequest.getParameters().getParameter(name);
}

parametersParsed参数初始化是false进入parseParameters()方法解析参数,将age=18放到paramHashValues这个Map容器中。 后续的重复请求就会省略解析参数的操作。

parseParameters()方法执行完成之后,接着从前面的 paramHashValues容器里面把age对应的18返回回去:

public String getParameter(String name) {
    handleQueryParameters(); // 这里也需要注意,存在一个类似的逻辑
    ArrayList<String> values = paramHashValues.get(name);
    if (values != null) {
        if (values.size() == 0) {
            return "";
        }
        return values.get(0); // 返回的是 age 的值 18
    } else {
        return null;
    }
}

这里重点看下handleQueryParameters方法的实现:

public void handleQueryParameters() {
    if (didQueryParameters) {
        return;
    }

    didQueryParameters = true;

    if (queryMB == null || queryMB.isNull()) {
        return;
    }

    try {
        decodedQuery.duplicate(queryMB);
    } catch (IOException e) {
        e.printStackTrace();
    }

    processParameters(decodedQuery, queryStringCharset);
}

这个方法在parseParamters中也会调用:

protected void parseParamters() {
    parametersParsed = true;

    Parameters parameters = coyoteRequest.getParameters();
    boolean success = false;
    try {
        // Set this every time in case limit has been changed via JMX
        parameters.setLimit(getConnector().getMaxParameterCount());

        //...
        Charset charset = getCharset();

        boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
        parameters.setCharset(charset);
        
        //...
        
        // 这里也调用了 handleQueryParameters 方法。
        parameters.handleQueryParameters();

    }
}

handleQueryParameters方法才是真正解析参数的方法,为了防止重复解析它加入了这样的逻辑:

if (didQueryParameters) {
    return;
}

didQueryParameters = true;

didQueryParameters初始为false,随后被设置为true。这个和之前的业务逻辑一致,入参解析一次并存放至Map中。

方法叫做recycle,表明是循环再利用,在这里面会把存放参数的Map清空,把didQueryParameters再次设置为了falseorg.apache.tomcat.util.http.Parameters#recycle方法如下:

public void recycle() {
    parameterCount = 0;
    paramHashValues.clear();
    didQueryParameters = false;
    charset = DEFAULT_BODY_CHARSET;
    decodeQuery.recycle();
    parseFailedReason = null;
}

而当你用同样的手段去观察parametersParsed参数,也就是这个参数的时候,会发现它也有一个recycle方法:org.apache.catalina.connector.Request#recycle

public void recycle() {
    internalDispatcherType = null;
    requestDispatcherPath = null;

    authType = null;
    inputBuffer.recycle();
    usingInputStream = false;
    usingReader = false;
    userPrincipal = null;
    parametersParsed = false;
}

由于我们在异步线程里面还触发了一次getParameter方法:但是getTest方法已经完成了响应,这个时候Request可能已经完成了回收。为了避免这个“可能”,我添加了sleep,保证request完成回收。

@GetMapping("/getTest")
public String getTest(HttpServletRequest request) {
    String age = request.getParameter("age");
    System.out.println("age=" + age);
    new Thread(() -> {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        String age1 = request.getParameter("age");
        System.out.println("age1=" + age1);
    }).start();
    return "success";
}

再次触发handleQueryParameters的时候,didQueryParameters由于被recycle了,所以变成了false

然后执行解析的逻辑,把didQueryParameters设置为true

但是,我们可以看到,此时查询的内容却没有了,是个null:这里的null也很好理解,肯定是随着调用结束,被recycle了。

为什么再次发起请求的时候,都返回null。

因为TomcatRquest使用的是池化思想,如果你拿到的是上一次的Request请求,那么因为在异步线程里面调用getParameter的时候,把didQueryParameters设置为true了。

但是异步线程里面的调用,超出了request的生命周期,所以并不会再次触发requestrecycle相关操作,因此这个request拿来复用的时候didQueryParameters还是true

所以,第二次请求的入参有值的,但是没用啊,didQueryParameterstrue,程序直接return了,不会去解析你的入参:

二、Request 的生命周期

每个request对象只在servlet的服务方法的范围内有效,或者在过滤器的doFilter方法的范围内有效。

但是组件的异步处理功能被启用后,并且在request上调用了startAsync方法后比较特殊。我们先看下startAsync方法:

public AsyncContext startAsync() throws IllegalStateException

在发生异步处理的情况下,request对象的生命周期一直会延续到在AsyncContext上调用complete方法之前。

/**
 * Completes the async request processing and closes the response stream
 */
void complete();

也就是说如果需要在上述范围之外,也就是多线程中使用request对象,需要使用到如下两个方法:
【1】requeststartAsync方法;
【2】AsyncContextcomplete方法;

我们将之前的代码进行改造:

@GetMapping("/getTest")
public String getTest(HttpServletRequest request, HttpServletResponse response) {
    AsycnContext asycnContext = request.startAsync(request, response);
    String age = request.getParameter("age");
    System.out.println("age=" + age);
    new Thread(() -> {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        String age1 = request.getParameter("age");
        System.out.println("age1=" + age1);
        asycnContext.complete();
    }).start();
    return "success";
}

此时在进行调用的时候,就算调用两次,都会正常输出:

age=18
age1=18
age=18
age1=18

从现象上来说,就是getTest请求返回之后,request线程并没有被调用recycle方法进行回收。

recycle方法的调用链上很快就能找到这个方法:

@Override
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status) throws IOException {
    // ......
    if (dispatches != null) {
        // ......
    } else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) {
        state = dispatch(status);
        state = checkForPipelinedData(state, socketWrapper);
    }
}

complete()方法上面的注解closes the response stream也不难发现,只有调用complete()方法之后,response流才会关闭。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2148639.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ssm自助购药小程序 LW PPT源码调试讲解

第二章开发技术介绍 此系统的关键技术和架构&#xff0c;Java技术、B/S结构、Ssm框架和Mysql数据库&#xff0c;是本系统的关键开发技术&#xff0c;对系统的整体、数据库、功能模块、系统页面以及系统程序等设计进行了详细的研究与规划。 2.1 系统开发平台 在线自助购药小程…

Linux入门攻坚——33、Mini Linux制作-2

前面是通过自定义内核配置制作的一个非常精简的Linux&#xff0c;这个Linux只能执行已经移植的有限的几个命令程序&#xff0c;为了使其具有更多功能&#xff0c;且控制其大小&#xff0c;使用kernel busybox。 busybox模拟实现了linux的各种命令程序。BusyBox 是一个集成了三…

【WPF】01 微软官方介绍开篇

这篇引入微软的首页介绍&#xff0c;比较全面&#xff0c;用于个人学习查看的内容&#xff0c;方便查找&#xff0c;后续将根据实战情况&#xff0c;逐步积累应用到的方法实现的效果等。 WPF 介绍 Windows Presentation Foundation (WPF) 是下一代显示系统&#xff0c;用于生…

微型丝杆加工的基本环境要求

微型丝杆是现代工业中常见的传动装置&#xff0c;广泛应用于各种机械设备和自动化系统中。而在潮湿、腐蚀性气体或高温等特殊环境下&#xff0c;微型丝杆需要具备特殊的环境适应性和防腐性能&#xff0c;以确保其长期稳定可靠地工作。那么微型丝杆的加工对环境有什么要求呢&…

IDEA中其他操作

删除类文件 点击想要删除的类-鼠标右键-Delete即可删除。需要注意的是如果该类中有代码引用了其他相关代码的话&#xff0c;需要先删除其他相关代码才能删除该类。 修改类名称 点击选中类-鼠标右键-Refactor-Rename 修改模块名 与修改类名称是一样的操作&#xff0c;只是在…

物流系统打单软件 佳易王物流运单怎么打印教程

一、前言 物流系统打单软件 佳易王物流运单怎么打印教程 1、佳易王物流管理系统可同时打印物流单和标签 2、如果一台电脑上有多台打印机&#xff0c;软件可以设置物流或标签对应的打印机&#xff0c;系统自动识别打印机。 二、软件程序图文说明 1、上图为 物流单在空白单上打…

JS在线加密解密工具

快捷工具网得JS加密解密工具为您提供JS加密解密,js加解密工具,JS在线加解密,JS代码在线加解密,该工具基于eval方法的加密与解密功能&#xff0c;用户可将js代码加密成eval方法执行形式的代码&#xff0c;也可将eval方法加密过的代码进行解密操作。是一款非常简便实用的在线Java…

身份证实名认证的应用场景-身份证识别api

引言 在互联网时代&#xff0c;虚拟身份和真实身份的界限逐渐模糊。为了保证线上平台的安全性和可信度&#xff0c;身份证实名认证逐渐成为必不可少的验证方式。它通过身份信息的核验&#xff0c;确保用户是真实的个人&#xff0c;防止虚假身份带来的各类风险。本文将探讨身份证…

使用vite+react+ts+Ant Design开发后台管理项目(一)

前言 本文将引导开发者从零基础开始&#xff0c;运用、react、react-router、react-redux、Ant Design、less、tailwindcss、axios等前沿技术栈&#xff0c;构建一个高效、响应式的后台管理系统。通过详细的步骤和实践指导&#xff0c;文章旨在为开发者揭示如何利用这些技术工…

Zabbix 部署----安装Zabbix(业务主机)

目录 1、另外准备一台虚拟机(192.xx.xx.20) 设置主机名 关闭防火墙、selinux 准备zabbix-repo 安装zabbix-agent 配置主服务器地址 启动zabbix-agent&#xff1a;10050 1、另外准备一台虚拟机(192.xx.xx.20) 设置主机名 hostname web1 关闭防火墙、selinux syst…

RS®AREG100A 汽车电子雷达回波发生器

AREG100A 汽车电子雷达回波发生器 轻松进行可靠的汽车电子雷达传感器生产测试 综述 R&SAREG100A 汽车电子雷达回波发生器是一款强大的智能解决方案&#xff0c;可用于生产过程中的汽车电子雷达传感器测试。借助 R&SAREG100A&#xff0c;生产工程师可获得多种优势&am…

二、编译原理-词法分析

一、词法分析器的作用 1、词法分析器的作用 读入字符流&#xff0c;组成词素&#xff0c;输出词法单元序列 过滤空白、换行、制表符、注释等 将词素添加到符号表中&#xff0c;以便编译的各个阶段取用 2、词法单元、模式、词素 &#xff08;1&#xff09;词法单元 (token…

Halcon OCR检测 免训练版

一.前言&#xff1a; 目前新版的Halcon已经具备了DeepOcr的功能可以涵盖大部分的识别场景&#xff0c;缺点是有些特殊的应用场景依然需要大量的图片训练&#xff0c;而且Halcon22之前的版本DeepOCR是不支持训练的&#xff0c;我们都知道传统的OCR项目是通过Blob分析&#xff0…

Linux中的进程入门

冯诺依曼体系结构 操作系统(Operator System) 进程控制块&#xff08;PCB&#xff09; struct task_struct{//该进程的所有属性//该进程对应的代码和属性地址struct task_struct* next; }; struct task_struct 内核结构体——>创建内核结构体对象(task_struct&#xff09;…

【vue element-ui】表单连锁验证,el-form validate函数失效问题

实现效果:连锁表单验证,在LED版本号选择为升级版LED时候,标题名称不超过8 实现代码: <el-form:model="configuration"ref="form":rules="rule"size="small"label-width="130px"v-if="isture == 1"><…

Docker笔记-Docker Dockerfile

Docker笔记-Docker Dockerfile Dockerfile 是一个用来构建镜像的文本文件&#xff0c;文本内容包含了一条条构建镜像所需的指令和说明。 这里讲解如何运行 Dockerfile 文件来定制一个镜像。 DockerFile构建过程解析&#xff1a; 1、每条保留字指令都必须为大写字母且后面要…

基于SpringBoot的图书进销存管理系统【附源码】

基于SpringBoot的图书进销存管理系统&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概述 4.2系统结构 4.3.数据库设计 4.3.1数据库实体 4.3.2数据库设计表 5系统详细实现 5.1 用户信息管理 5.2 图书类型管理 5.3 商品…

【雪球-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

JavaWeb 实验一

实验一 环境配置和Web工程的创建 目的&#xff1a; 掌握Java Web编程环境的配置创建简单的Web工程&#xff0c;并了解Web工程下各目录的作用掌握部署、运行Web工程的流程 实验过程&#xff1a; 一、完成如下要求。 安装并设置JDK 1.8、Tomcat 9.0&#xff08;tomcat和jdk版…

如何才能开发出最适合自己公司的网站?

开发最适合自己公司的网站是一个综合性的项目&#xff0c;需要从多个角度进行考虑和规划。以下是一些关键步骤和建议&#xff1a; 一、明确目标与定位 确定网站目标&#xff1a;明确网站的核心目的&#xff0c;如提升品牌知名度、展示公司产品或服务、促进销售、提供客户服务…