问题背景

项目是 SpringBoot 单体式,在项目中,为了实现调用 controller 请求的日志记录功能。因此做了以下配置:

  1. 创建自定义拦截器 LogInterceptor;
  2. 因为需要使用到流获取请求参数,解决流只能读一次问题,所以需要自定义 HttpServletRequestWrapper;
  3. 需要使得自定义 HttpServletRequestWrapper 生效,因此还需要自定义 Filter (HttpServletRequestFilter);

目前在项目中可以正常使用。现在需要把项目的这些配置复制到另一个项目,其中Springboot版本一致,其他三方依赖什么都一样。但是新项目导入文件时,报参数缺失。

排查思路

  1. 查询新老项目配置的 filter是否一样?

    发现是一样的。并且没有配置多余的filter。

  2. 查询新老项目导入文件接口的 controller 请求入参方式是否一样?

    发现导入文件时,都是form 表单,content-type 都是 multipart/form-data。(导入文件仅支持form表单,因此第一次排查属于多余!)

  3. 怀疑拦截器 LogInterceptor 使用了 request,导致 request数据丢失。

​ 通过debug发现,其实走到 logInterceptor 时,request 数据已经丢失。

  1. 既然还没走到拦截器,那可能是自定义 HttpServletRequestWrapper 或者 自定义 HttpServletRequestFilter 问题,接着 debug,发现配置都没问题,可以正常执行。

  2. 依次排查了 filter 的加载顺序,发现也没啥用。

  3. 排除了 自定义 requestWrapper 和 filter,又发现可以正常获取数据了。然后试着将 requestWrapper 中构造方式的流转换为字节的操作放在每次获取 getInputStream() 方法中,发现也可以。因此怀疑是底层加载出现问题了。

  4. 试着对比新老项目所使用的 filter,我通过debug打断点的方式,可以在ApplicationFilterChain的internalDoFilter()方法上打断点。

发现两边不太一样。查询 hiddenHttpMethodFilter过滤器,发现是在 WebMvcAutoConfiguration 自动装配的,但是必须设置 spring.mvc.hiddenmethod.filter.enabled=ture。然后查询项目中 application.yml 配置,发现新老项目这块缺失不一样。然后需要新项目配置,发现问题解决。

根因分析

思考:

  • 为啥调整 requestWrapper 中流转换为字节操作的位置,从构造方式移到 getInputStream() 方法。就可以。

  • 为啥非要使用 hiddenHttpMethodFilter 过滤器,也就能解决问题

分析:

通过百度和查看 hiddenHttpMethodFilter 源码发现,org.apache.catalina.connector.Request.parseParameters() 方法中,判断事先调用过了getInputStream或者getReader,再调用getParameter就不会进行解析了。也就是在一个请求链中,请求对象被前面对象方法中调用request.getInputStream()或request.getReader()获取过内容后,后面的对象方法里再调用这两个方法也无法获取到客户端请求的内容,但是调用request.getParameter()方法获取过内容后,后面的对象方法里依然可以调用它获取到参数的内容。这也就验证了以上两个问题

  • 问题1,移动位置后,在 自定义 filter中, 将 httpServletRequest 对象转换成对象时,不执行 getInputStream() 方法,也就解决了问题。
  • 问题2,使用 hiddenHttpMethodFilter 过滤器,里边执行了一行代码 String paramValue = request.getParameter(this.methodParam);,这行代码保证了parseParameters()方法优先执行,也能解决问题。

根因:

Tomcat的ServletRequest中, getParameter()方法与getInputStream()/getReader()不兼容, 只能选择一方.调用了一方, 另一方就会是空的(前提:表单的POST请求)。

解决方案

原理已经清楚了,按时会产生出各种不同的问题,因此解决方案需要根据问题场景来说。常见的有:

  1. 调整自定义 RequestWrapper 中流转换为字节操作的位置,使其放在 getInputStream() 方法中,这样就不影响底层框架使用。

  2. 在 自定义 filter 中,继承 OncePerRequestFilter, 并重写 shouldNotFilter(HttpServletRequest request) 方法,然后只对 Json 请求内容做流只能读取一次的处理(按需引入 Wrapper)。

扩展知识

application/x- www-form-urlencoded 是 Post 请求默认的请求体内容类型,该请求方式是通过调用 request.getParameter() 方法来获取请求参数值。

当请求体内容(注意:get请求没有请求体)类型是 application/x- www-form-urlencoded 时也可以直接调用request.getInputStream()或request.getReader()方法获取到请求内容再解析出具体都参数,但前提是还没调用request.getParameter()方法。此时当request.getInputStream()或request.getReader()获取到请求内容后,无法再调request.getParameter()获取请求内容。即对该类型的请求,三个方法互斥,只能调其中一个。

application/json 是 Post 请求 body 体传参方式,该请求方式是通过调用 request.getInputStream()或request.getReader() 方法来获取请求内容值。

multipart/form-data 是Post 请求文件上传的传参方式,这种比较特殊,经测试,可以使用 request.getParameter() 获取到除文件外的其他参数,获取文件参数,需要使用 request.getInputStream() 。

参考链接:

记一次getParameter()获取不到参数问题的排查

自定义httpservletrequestwrapper导致form表单提交数据丢失_添加requestwapper后无法获取提交参数

自定义httpServletRequestWrapper导致上传文件请求参数丢失的更多相关文章

  1. Django之用户上传文件的参数配置

    Django之用户上传文件的参数配置 models.py文件 class Xxoo(models.Model): title = models.CharField(max_length=128) # ...

  2. js上传文件带参数,并且,返回给前台文件路径,解析上传的xml文件,存储到数据库中

    ajaxfileupload.js jQuery.extend({ createUploadIframe: function(id, uri) { //create frame var frameId ...

  3. 【转】php通过curl跨域向asp.net服务器上传文件及参数

    转:http://blog.sina.com.cn/s/blog_13331dce50102vq32.html 这是一个由php通过调用asp.net接口向asp.net服务器post上传文件及参数并 ...

  4. httpclient请求接口,上传文件附加参数(.net core)

    /// <summary> /// 上传文件 - 武汉站点 /// </summary> [HttpPost] public IActionResult UploadWH(Re ...

  5. postman上传文件对参数的contentType类型设置方式

    项目中使用postman模拟上传文件接口时,总是不成功,发现content-type设置不对,设置head的contentType后,还是不行,后来无意中发现文件参数默认的content-type类型 ...

  6. Java模拟http上传文件请求(HttpURLConnection,HttpClient4.4,RestTemplate)

    先上代码: public void uploadToUrl(String fileId, String fileSetId, String formUrl) throws Throwable { St ...

  7. 自定义type='file'上传文件样式

    改变默认的上传文件样式: 用label作为替代 <input id="file_-1" type="file" name="file" ...

  8. SharePoint REST 上传文件请求403错误

    最近,需要在SharePoint上传文件到文档库,但是,上传的过程报错了. 错误代码 { "error": { "code": "-213057525 ...

  9. element-ui upload自定义formdata上传文件和参数

      <el-upload list-type="text" action="" :http-request="HandleHttpRequest ...

  10. curl_setopt用此函数设置上传文件请求的兼容性调整

    在用curl_setopt($curl, CURLOPT_POSTFIELDS, $fileData);这个函数设置时会报错如下 curl_setopt(): The usage of the @fi ...

随机推荐

  1. AI Studio 基本操作

    https://aistudio.baidu.com/aistudio/projectdetail/6182202 项目启停 执行和调试 添加代码或文件 运行代码 %cd /home/aistudio ...

  2. Axure 交互样式

  3. Mac下安装mysqlclient出错解决「mysql_config not found」

    错误信息如下图 解决方法 安装mysql-client brew install mysql-client 设置环境变量 export PATH=$PATH:/usr/local/Cellar/mys ...

  4. Multisim 14 免费破解版,下载安装教程,2023年亲测可用,永久激活

    Multisim是一款功能强大.操作流畅的专业仿真工具,适用于板级模拟/数字电路板设计工作,提供电路原理图图形输入.电路硬件描述语言输入,具有丰富的仿真分析能力.拥有专业版和教学版,深受国内外教师.科 ...

  5. SuperSocket 简单示例

    这是一个SuperSocket 简单示例,包括服务端和客户端. 一.首先使用NuGet安装SuperSocket和SuperSocket.Engine 二.实现IRequestInfo(数据包): 数 ...

  6. #2089: 不要62 (数位dp模板题,附带详细解释)

    题目链接 题意:问区间[n,m]中,不含数字4,也不含数字串"62"的所有数的个数. 思路:可以转化成求区间[0,x] 第一次接触数位dp,参考了这几篇博客. 不要62(数位dp) ...

  7. 二、swift添加存储策略

    系列导航 一.swift对象存储环境搭建 二.swift添加存储策略 三.swift大对象--动态大对象 四.swift大对象--静态态大对象 五.java操作swift对象存储(官网样例) 六.ja ...

  8. Java面试——数据库知识点

    MySQL 1.建 主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合.一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null). 超键:在关系中能唯一标识元组的属性 ...

  9. 基于java+springboot的图书借阅网站-在线图书借阅管理系统

    该系统是基于java+springboot开发的图书借阅管理系统.是给师弟开发的课程作业.大家学习过程中,遇到问题可以github咨询作者. 系统演示地址 前台 http://book.gitapp. ...

  10. Kafka 社区KIP-382中文译文(MirrorMaker2/集群复制/高可用/灾难恢复)

    译者:对于Kafka高可用的课题,我想每个公司都有自己的方案及思考,这是一个仁者见仁智者见智的命题,而社区给出了一个较大的特性,即MirrorMaker 2.0,不论是准备做高可用还是单纯的数据备份, ...