问题背景

项目是 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. Mac 复制文件名目录路径

    Mac快速复制文件路径 在Mac电脑上,我们经常需要复制文件的路径,其实,Mac系统提供了快速复制文件路径的方法.下面我们来详细介绍. 方法一:使用菜单栏 首先,打开Finder,然后选择你要复制路径 ...

  2. Axure 绘制表格添加删除

    1.添加按钮:白底黑框,80X30,文本和命名均为"添加"; 2.数据表格(表头):1行5列,灰底黑框的表格作为表头,从左到右每个格子的文字分别为:姓名.性别.年龄.电话和操作: ...

  3. 微软的一些公开课,Python、机器学习、SQL、AI,全部免费

    大家好,我是老章,刷X看到一位博主Alif Hossain@alifcoder总结了微软的一些公开课,全部免费,蛮不错的.感兴趣可以学一波,还能领徽章. 1. 机器学习简介 本课程是学习机器学习基础知 ...

  4. TypeError: this.libOptions.parse is not a function

    安装完node.js运行项目后,报错: TypeError: this.libOptions.parse is not a function at ESLint8Plugin.<anonymou ...

  5. Codeforces 1312B Bogosort (逆序证明)

    Example input 3 1 7 4 1 1 3 5 6 3 2 1 5 6 4 output 7 1 5 1 3 2 4 6 1 3 5 看题的时候发现和sort有关,但一定要逆序排序 证明: ...

  6. <vue 基础知识 4、计算属性computed>

    代码结构 一.     计算属性简单使用 1.效果 2.代码 01-计算属性简单用法.html <!DOCTYPE html> <html lang="en"&g ...

  7. 使用element-plus的el-scrollbar时滚动条没有显示出来但是页面可以滚动的解决办法

    如果使用 Element UI 的 el-scrollbar 组件时,滚动条没有显示出来但页面可以滚动,可以尝试调用其 update 方法来更新滚动条. 在适当的时机(例如在数据加载完成后或组件更新后 ...

  8. 传说中 PUE 预测精度高达 0.005 的工作

    杨震, 赵静洲, 林依挺 等. 数据中心 PUE 能效优化的机器学习方法. 系统工程理论与实践, 2022, 42(3): 811-832 省流: 这是 2020 年的论文,用神经网络进行了认真的 P ...

  9. ElasticSearch 通过 Kibana 与 ElasticSearch-head 完成增删改查

    本文为博主原创,未经允许不得转载: 1.  安装并配置 elasticSearch ,kibana, elasticsearch-head docker 安装 ElasticSearch 和 Kiba ...

  10. Socket 如何处理粘包

    Socket 如何处理粘包 什么是粘包什么是半包? 粘包: 比如发送了AA BB 两条消息,但是另一方接收到的消息却是AAB,像这种一次性读取了俩条数据的情况就是粘包 半包: 比如发送的消息是ABC时 ...