问题背景

项目是 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. Kubernetes(K8S) 镜像拉取策略 imagePullPolicy

    镜像仓库,镜像已更新,版本没更新, K8S 拉取后,还是早的服务,原因:imagePullPolicy 镜像拉取策略 默认为本地有了就不拉取,需要修改 [root@k8smaster ~]# kube ...

  2. SQL SERVER数据分组后取第一条数据——PARTITION BY

    MySQL 数据分组后取第一条数据 SQL SERVER (mssql) 数据分组后取第一条数据 SQL 如下 找状态=1的数据,按 HospitalId,DeptId 组合并倒序排序,每组里面取第一 ...

  3. 第04讲:Flink 常用的 DataSet 和 DataStream API

    Flink系列文章 第01讲:Flink 的应用场景和架构模型 第02讲:Flink 入门程序 WordCount 和 SQL 实现 第03讲:Flink 的编程模型与其他框架比较 第04讲:Flin ...

  4. Nginx--upstream健康检查

    nginx 判断节点失效状态: Nginx 默认判断失败节点状态以connect refuse和time out状态为准,不以HTTP错误状态进行判断失败,因为HTTP只要能返回状态说明该节点还可以正 ...

  5. 【ToolChains】| CMake 技巧

    判断 CMake 编译环境 编译类型 CMAKE_BUILD_TYPE 可取值为:Debug, Release, RelWithDebInfo, MinSizeRel 等预设值 if (CMAKE_B ...

  6. 0x61 图论-最短路

    B题 Telephone Lines https://ac.nowcoder.com/acm/contest/1055/B 中文题面:https://www.luogu.com.cn/problem/ ...

  7. Spark 数据倾斜及其解决方案

    本文首发于 vivo互联网技术 微信公众号 https://mp.weixin.qq.com/s/lqMu6lfk-Ny1ZHYruEeBdA 作者简介:郑志彬,毕业于华南理工大学计算机科学与技术(双 ...

  8. 反应式编程 RxJava 设计原理解析

    本文首发于 vivo互联网技术 微信公众号 链接: https://mp.weixin.qq.com/s/duO1pAfaKUI2_x_GVvZHMg作者:Yunjie Ma 一.ReactiveX ...

  9. Springboot+ELK实现日志系统简单搭建

    前面简单介绍了ELK三剑客中的其中两个Elasticsearch和Kibana的简单使用,如果对这两个不了解,可以看下下面的 Centos7安装Elasticsearch和Kibana 搜索引擎基础- ...

  10. 简单剖析Hashmap

    剖析 Java Hashmap 源码 在 Java 的集合框架中,HashMap 是一颗璀璨的明珠.通过深入挖掘其源码,我们将揭开 HashMap 的神秘面纱,理解其底层原理.扩容机制和数据结构. 1 ...