Ajax 直接发送 PUT 请求,后端无法接收到数据的原因及解决方案

一、问题描述:

使用 Ajax 直接发送 PUT 请求,但 Spring MVC 封装的对象中,除过 URI 中带有的 id 字段被成功封装,请求体中的数据没有被封装到对象中。

通过测试,前端传来的请求体中有数据;通过 HttpServletRequest 对象,使用 request.getParameter() 方法却也获取不到数据

二、解决方案:

在 web.xml 中添加 HttpPutFormContentFilter 过滤器,原理向下看:

  1 <filter>
  2     <filter-name>httpPutFormContentFilter</filter-name>
  3     <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
  4 </filter>
  5 <filter-mapping>
  6     <filter-name>httpPutFormContentFilter</filter-name>
  7     <url-pattern>/*</url-pattern>
  8 </filter-mapping>

三、原因分析:

1. Tomcat 会将请求体中的数据封装成一个 Map,调用 request.getParameter() 会从这个 Map 中取值,Spring MVC 封装 POJO 对象时,对于对象中的每个属性值也会调用 request.getParameter() 方法去获取,然后赋值给对象相应的属性,完成属性封装。

2. 但是,对于 PUT 请求,Tomcat 不会封装请求体中的数据为一个 Map,只会将 POST 请求的请求体中数据封装成 Map;这样的话,无论是直接调用 request.getParameter() 方法,还是 Spring MVC 封装对象,肯定都拿不到属性值了。

四、源码分析:

1. 在 Tomcat 源码中 org.apache.catalina.connector 包的 Request.java 类中 parseParameters() 方法用来解析请求参数,方法先会进行一系列设置和判断,然后再解析请求参数。其中,下面这段代码就会判断请求类型,如果符合 if 条件就会直接返回,也就是判断 parseBodyMethod 中是不是包含当前的请求方式,如果包含,Tomcat 服务器就继续处理解析请求体中的参数,封装到 Map 中;如果不包含,就直接返回,而不再执行下面的方法解析参数,那么 Map 中也就没有相应的参数了。

  1 if( !getConnector().isParseBodyMethod(getMethod()) ) {
  2     success = true;
  3     return;
  4 }

2. getConnector() 方法用来获取当前连接对象,然后调用 isParseBodyMethod() 方法判断 parseBodyMethodSet 集合中是不是包含传进来的 method 的值,这个 method 值就是当前的请求方式。

  1 /**
  2  * Request.java类中
  3  * 获取当前的请求方式
  4  */
  5 public String getMethod() {
  6     return coyoteRequest.method().toString();
  7 }
  8
  9 /**
 10  * Connector.java类中
 11  */
 12 protected boolean isParseBodyMethod(String method) {
 13      return parseBodyMethodsSet.contains(method);
 14 }

3. parseBodyMethodSet 集合中一般使用的都是默认值,也就是相当于将 parseBodyMethods 的值赋给了 parseBodyMethodSet,而 parseBodyMethods 的值默认是 post。

  1 /**
  2   * 调用 setParseBodyMethods 将 getParseBodyMethods() 获取的 parseBodyMethods 的值赋值给
  3   * parseBodyMethods
  4   */
  5 protected void initInternal() throws LifecycleException {
  6
  7     // ...
  8
  9     // Make sure parseBodyMethodsSet has a default
 10     if( null == parseBodyMethodsSet ) {
 11         setParseBodyMethods(getParseBodyMethods());
 12     }
 13
 14     // ...
 15 }
 16
 17 /**
 18  * 获取 parseBodyMethods 的值
 19  */
 20 protected String parseBodyMethods = "POST";
 21
 22 public String getParseBodyMethods() {
 23     return this.parseBodyMethods;
 24 }
 25
 26 /**
 27  * 定义连接器的规则,如果连接器允许解析非POST请求的请求体,就传入规则,默认都是没有的
 28  * 所以 parseBodyMethodSet 使用的是默认值
 29  * 将 methods 的值赋值给 parseBodyMethods
 30  * 将 methodSet 的值赋值给 parseBodyMethodsSet
 31  */
 32 public void setParseBodyMethods(String methods) {
 33
 34     HashSet<String> methodSet = new HashSet<String>();
 35
 36     if( null != methods ) {
 37         methodSet.addAll(Arrays.asList(methods.split("\\s*,\\s*")));
 38     }
 39
 40     // ...
 41
 42     this.parseBodyMethods = methods;
 43     this.parseBodyMethodsSet = methodSet;
 44 }

4. 通过前面几步分析,parseBodyMethodSet 中默认只有 post,也只有当前请求方式是 post 的时候才会解析参数,因此,不管是直接通过 Request 对象的 getParameter() 还是 Spring MVC 封装 POJO 对象都不会获取到参数值。

五、HttpPutFormContentFilter 过滤器原理

  1 /**
  2  * 封装请求体数据,重新包装 Request 对象
  3  */
  4 protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse 	response, FilterChain filterChain) throws ServletException, IOException {
  5     // 当是 put 请求或 patch 请求的时候
  6     if (("PUT".equals(request.getMethod()) || "PATCH".equals(request.getMethod())) && this.isFormContentType(request)) {
  7         HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
  8             // 获取请求体中的数据流,封装成HttpInputMessage
  9             public InputStream getBody() throws IOException {
 10                 return request.getInputStream();
 11             }
 12         };
 13
 14         // 将上一步封装的 HttpInputMessage 读取封装成一个MultiValueMap 对象,
 15         // 这个对象继承自 Map
 16         // 将请求体中的数据封装成 Map
 17         MultiValueMap<String, String> formParameters = this.formConverter.read((Class)null, inputMessage);
 18
 19         // 使用 HttpPutFormContentRequestWrapper 重新包装 Request 对象
 20         HttpServletRequest wrapper = new HttpPutFormContentFilter.HttpPutFormContentRequestWrapper(request, formParameters);
 21         filterChain.doFilter(wrapper, response);
 22     } else {
 23         filterChain.doFilter(request, response);
 24     }
 25 }
 26
 27 /**
 28  * 包装 Request 对象具体实现
 29  * 重写父类的 getParameter() 等方法,先调用父类的方法
 30  * 如果能获取到就是用父类获取的;
 31  * 如果获取不到,就是用当前类获取的。
 32  */
 33 private static class HttpPutFormContentRequestWrapper extends 	HttpServletRequestWrapper {
 34       private MultiValueMap<String, String> formParameters;
 35
 36       public String getParameter(String name) {
 37              String queryStringValue = super.getParameter(name);
 38              String formValue = (String)this.formParameters.getFirst(name);
 39              return queryStringValue != null ? queryStringValue : formValue;
 40       }
 41
 42       // ...
 43 }

原文地址:https://www.cnblogs.com/lveyHang/p/11791412.html

时间: 2024-08-02 04:45:19

Ajax 直接发送 PUT 请求,后端无法接收到数据的原因及解决方案的相关文章

使用AJAX技术发送异步请求,HTTP服务端推送

使用AJAX技术发送异步请求 什么是AJAX AJAX指一步Javascript和XML(Asynchronous JavaScript And XML),它是一些列技术的组合,简单来说AJAX基于XMLHttpRequest让我们在不重载页面的情况下和服务器进行数据交换. 加上JavaScript和DOM(Document Object Model,文档对象模型),我们就可以在接收到响应数据后局部更新页面.XML指的是数据的交互模式,可以是纯文本(Plain Text).HTML或JSON.

echarts通过ajax向服务器发送post请求,servlet从数据库读取数据并返回前端

1.echarts的官网上的demo,都是直接写死的随机数据,没有和数据库的交互,所以就自己写了一下,ok,我们开始一步一步走一遍整个流程吧. 就以官网最简单的那个小demo来做修改吧.官网上的小demo的效果图如下:(很熟悉,有没有) 2.按照echarts的使用方法新建一个echarts.html文件.为ECharts准备一个具备大小(宽高)的Dom(讲的有点细,熟悉的朋友直接跳过) <!DOCTYPE html> <head> <meta charset="u

axiso发送网络请求及python接收处理

安装$ npm install axios 1.发送get请求: axios.get("/api/v1.0/cars?id=132").then(function(res){ console.log(res) }).catch(function(err){ console.log(err) }); 2.发送post请求: let params = { id:4, ctime:'2019-03-1',name:"奔驰4" } //'Content-Type':'app

以ajax请求方式进行文件下载操作失败的原因及解决方案

一.失败的原因 那是因为response原因,一般请求浏览器是会处理服务器输出的response,例如生成png.文件下载等,然而ajax请求只是个“字符型”的请求,即请求的内容是以文本类型存放的.文件的下载是以二进制形式进行的,虽然可以读取到返回的response,但只是读取而已,是无法执行的,说白点就是js无法调用到浏览器的下载处理机制和程序. 二.解决方案 1)可以使用jquery创建表单并提交实现文件下载: var form = $("<form>"); form.

axios发送post请求,提交表单数据

解决办法一 你要是看下用法就解决了... https://www.npmjs.com/package- 或者 https://github.com/mzabriskie- 1 axios({ 2 url: '/user', 3 method: 'post', 4 data: { 5 firstName: 'Fred', 6 lastName: 'Flintstone' 7 }, 8 transformRequest: [function(data) { 9 10 let ret = '' 11

android onActivityResult()接收返回数据为null的解决方案

对于·app多个界面管理,如果一般使用Activity默认的加载模式,按返回键就会退回上一次操作,就是一种新建一个Activity实例.时间长了就会变得卡顿,一般人会选择手动地在代码中屏蔽返回键,使用app中开发的返回键,这样也可以解决问题. 但是本质问题还是没解决,不可能以后开发也用这样的方法,用多了也烦躁.所以解决这个问题的关键在于了解Activity的加载模式. 第一中加载模式是:standard标准模式,系统默认的加载的模式 android:launchMode="standard&qu

curl命令发送Post请求

目的1:通过脚本发送post请求.  答案: curl -d "leaderboard_id=7778a8143f111272&score=19&app_key=8d49f16fe034b98b&_test_user=test01" "http://172.16.102.208:8089/wiapi/score" 目的2:通过脚本发送post请求,顺便附带文本数据,比如通过"浏览"选择本地的card.txt并上传发送pos

使用handler和Message获取xutils发送POST请求从服务器端返回数据

注意:应该在handleMessage中处理从服务器返回的数据.否则会因为线程问题拿不到结果. public class MainActivity extends Activity{ private String responseInfo; private Handler handler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setCo

ASP.NET WebForm 之 Ajax 请求后端处理

概述 ASP.NET MVC中的异步用途非常广泛,操作起来也非常简单.前台请求异步请求 Controller下的Action 方法,后端返回ActionResult 即可.但是在ASP.NET WebForm中使用异步就比较麻烦,下面介绍两种处理WebForm异步请求的形式.若前端Ajax异步请求不熟悉的请参考:http://www.w3school.com.cn/jquery/ajax_ajax.asp 通过一般处理程序的形式处理异步请求 1.新建一个WebForm 窗体 2.创建表单元素 <