使用Spring处理x-www-form-urlencoded方式


Posted in Java/Android onNovember 02, 2021

Spring处理x-www-form-urlencoded方式

最近在重写一个项目时遇到了许多奇葩问题,这个项目是一个简单的web后台项目,基本上全都是增删改查数据库的操作。这里面遇到几个用spring接收前端post请求的接口。

基本情况是post请求有四种data参数格式,这些基础知识在我另一片博文中提到过这里就不废话了。主要是因为前端有两个地方用到了这个接口,但是在用这个接口的时候两个地方用法都不同,奇葩的c++居然还都解析成功了(其实因为c++没有对请求参数格式和数据做检查所以一直没有问题)。

一个地方是发送的是application/json格式,发送了一个jsonArray数据(数据例子["abc", "bcd"])这个是没有问题的正确使用方式。(下面简称前者)

另一个地方是发送的application/x-www-form-urlencode格式,发送的也是一个jsonArray数据。(下面简称后者)

前者解析方式比较简单

@RequestMapping(value = "/check_apps_version",
      method = RequestMethod.POST,
      produces = {"application/json;charset=UTF-8"},
      consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
  @ResponseBody
  public BaseResponse<List<AppListItem>> checkAppsVersion(ReqCheckAppsVersion requstParam,
      @RequestBody List<String> apps) {
    return new BaseResponse<>();
  }

后者这个发送方式在spring用@RequestBody解析时就很怪异,但是前段是手机APP已经发布出去了没法修改,只能后端来修改满足这个奇怪的需求

通过调试发现后者前端的接口传过来的参数是"["abc","def"]="这样子的,本身x-www-form-urlencode是多个kev-value对的数据格式,所以现在没有value只有key了,只能通过字符串处理来解决了。

@RequestMapping(value = "/check_apps_version",
      method = RequestMethod.POST,
      produces = {"application/json;charset=UTF-8"},
      consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
  @ResponseBody
  public BaseResponse<List<AppListItem>> checkAppsVersionParams(HttpServletRequest request,
                                                                ReqCheckAppsVersion requstParam,
                                                                @RequestBody String apps) {
 String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
    return BaseResponse.success();
  }

两种方式,一种是通过@RequestBody把post data解析成string格式,另一种是通过HttpServletRequest解析出整个原始post data。然后做字符串处理。

但是在做做这个测试时候我们想了一下会不会有只有value没有key的情况,也就是这样"=["abc","def"]"。

测试结果是用tomcat没法从HttpServletRequest到这个post data,但是用jetty可以从HttpServletRequest解析到post data。

这个可能是tomcat和jetty的区别吧,还没有弄清楚什么原因。但是我们的问题总算是解决了,最大感触就是前人挖坑后人埋啊。

希望以后能注意一下代码健壮性的问题,避免给别人或者自己挖坑。

关于application/x-www-form-urlencoded编码

同事遇到在servlet端通过request对象getInputStream读取POST过来的数据,却读不到的问题,怀疑是tomcat的问题。查了一下Content-type是application/x-www-form-urlencoded,估计是被解析成了parameters,果然在他获取流之前,有过request.getParameter的操作。

熟悉servlet的话,这个问题应该算常识了。它其实跟容器无关,所有的servlet容器都是这样的行为。几年前在实现一个网关代理的时候就遇到过这个问题,当时使用的是jetty,发现POST过来的数据读不到,也是application/x-www-form-urlencoded编码,断点跟踪发现是在获取流之前有过request.getParameter,数据会被解析,并且后续数据流不可再被读取。

在servlet规范3.1.1节里,对POST数据何时会被当做parameters有描述:

1. The request is an HTTP or HTTPS request.
2. The HTTP method is POST.
3. The content type is application/x-www-form-urlencoded.
4. The servlet has made an initial call of any of the getParameter family of methods on the request object.

If the conditions are met, post form data will no longer be available for reading directly from the request object's input stream.

规范里已经明确的声明当请求满足:

1) http/https

2) POST

3) Content-type 是application/x-www-form-urlencoded

4) 调用过getParameter方法,则数据会被当做请求的paramaters,而不能再通过 request 的 inputstream 直接读取。

所以不论tomcat、jetty还是其他servlet容器都遵循这个方式。不过话说回来,为什么application/x-www-form-urlencoded编码的数据会被当做parameter来解析呢?

使用http上传数据可以用GET或POST,使用GET的话,只能通过uri的queryString形式,这会遇到长度的问题,各个浏览器或server可能对长度支持的不同,所以到要提交的数据如果太长并不适合使用GET提交。

采用POST的话,既可以在uri中带有queryString也可以将数据放在body中。body内容可以有多种编码形式,其中application/x-www-form-urlencoded编码其实是基于uri的percent-encoding编码的,所以采用application/x-www-form-urlencoded的POST数据和queryString只是形式不同,本质都是传递参数。

在tomcat的Request.parseParameters方法里,对于application/x-www-form-urlencoded是有做判断的,对这种编码会去解析body里的数据,填充到parameters里,所以后续想再通过流的方式读取body是读不到的(除非你没有触发过getParameter相关的方法)。

在HTML4之前,表单数据的编码方式只有application/x-www-form-urlencoded这一种(现在默认也是这种方式),因为早期的时候,web上提交过来的数据也是非常简单的,基本上以key-value形式为主,所以表单采用application/x-www-form-urlencoded这种编码形式也没什么问题。

在HTML4里又引入了multipart/form-data编码,对于这两种编码如何选择,请参考这里

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Java/Android 相关文章推荐
分析JVM源码之Thread.interrupt系统级别线程打断
Jun 29 Java/Android
Java网络编程之UDP实现原理解析
Sep 04 Java/Android
详解JAVA的控制语句
Nov 11 Java/Android
SpringBoot2零基础到精通之数据库专项精讲
Mar 22 Java/Android
Spring Boot DevTools 全局配置学习指南
Mar 31 Java/Android
Android超详细讲解组件ScrollView的使用
Mar 31 Java/Android
MyBatis配置文件解析与MyBatis实例演示
Apr 07 Java/Android
Java字符缓冲流BufferedWriter
Apr 09 Java/Android
零基础学java之方法的定义与调用详解
Apr 10 Java/Android
Java 多态分析
Apr 26 Java/Android
详解Flutter自定义应用程序内键盘的实现方法
Jun 14 Java/Android
Spring Boot 的创建和运行示例代码详解
Jul 23 Java/Android
Java 实现限流器处理Rest接口请求详解流程
Java8中接口的新特性使用指南
Nov 01 #Java/Android
Spring中的使用@Async异步调用方法
Nov 01 #Java/Android
MyBatis自定义SQL拦截器示例详解
Oct 24 #Java/Android
java多态注意项小结
Spring Security中用JWT退出登录时遇到的坑
Java实现房屋出租系统详解
Oct 05 #Java/Android
You might like
ThinkPHP多表联合查询的常用方法
2020/03/24 PHP
php自动给网址加上链接的方法
2015/06/02 PHP
PHP基于socket实现的简单客户端和服务端通讯功能示例
2017/07/10 PHP
Javascript 判断 object 的特定类转载
2007/02/01 Javascript
javascript的键盘控制事件说明
2008/04/15 Javascript
开发跨浏览器javascript常见注意事项
2009/01/01 Javascript
JQuery中判断一个元素下面是否有内容或者有某个标签的判断代码
2012/02/02 Javascript
jQuery移动和复制dom节点实用DOM操作案例
2012/12/17 Javascript
对jQuery的事件绑定的一些思考(补充)
2013/04/20 Javascript
第九章之路径分页标签与徽章组件
2016/04/25 Javascript
BootStrap的Datepicker控件使用心得分享
2016/05/25 Javascript
JS中使用变量保存arguments对象的方法
2016/06/03 Javascript
BootStrap table表格插件自适应固定表头(超好用)
2016/08/24 Javascript
浅析BootStrap Treeview的简单使用
2016/10/12 Javascript
解决JS外部文件中文注释出现乱码问题
2017/07/09 Javascript
jQuery之动画ajax事件(实例讲解)
2017/07/18 jQuery
5分钟快速掌握JS中var、let和const的异同
2018/09/19 Javascript
vue调试工具vue-devtools安装及使用方法
2018/11/07 Javascript
vue移动端屏幕适配详解
2019/04/30 Javascript
JavaScript实现打字游戏
2021/02/19 Javascript
[45:40]Ti4 冒泡赛第二天NEWBEE vs NaVi 1
2014/07/15 DOTA
[06:57]DOTA2-DPC中国联赛 正赛 Ehome vs PSG.LGD 选手采访
2021/03/11 DOTA
Python 十六进制整数与ASCii编码字符串相互转换方法
2018/07/09 Python
Python命名空间的本质和加载顺序
2018/12/17 Python
Django项目uwsgi+Nginx保姆级部署教程实现
2020/04/19 Python
在Anaconda3下使用清华镜像源安装TensorFlow(CPU版)
2020/04/19 Python
python按顺序重命名文件并分类转移到各个文件夹中的实现代码
2020/07/21 Python
python 6种方法实现单例模式
2020/12/15 Python
移动端开发HTML5页面点击按钮后出现闪烁或黑色背景的解决办法
2018/09/19 HTML / CSS
Swanson中国官网:美国斯旺森健康产品公司
2021/03/01 全球购物
公务员政审个人鉴定
2014/02/25 职场文书
人民币使用说明书
2019/04/17 职场文书
使用react-virtualized实现图片动态高度长列表的问题
2021/05/28 Javascript
深入浅出的讲解:信号调制到底是如何实现的
2022/02/18 无线电
Vue2.0搭建脚手架
2022/03/13 Vue.js
数据分析数据库ClickHouse在大数据领域应用实践
2022/04/03 MySQL