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

目录
  • Spring处理x-www-form-urlencoded方式
  • 关于application/x-www-form-urlencoded编码

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编码,对于这两种编码如何选择,请参考这里

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 一篇文章弄懂Spring MVC的参数绑定

    前言 参数绑定,简单来说就是客户端发送请求,而请求中包含一些数据,那么这些数据怎么到达 Controller ?这在实际项目开发中也是用到的最多的,那么 SpringMVC 的参数绑定是怎么实现的呢? 下面我们来详细的讲解. SpringMVC参数绑定,简单来说就是将客户端请求的key/value数据绑定到controller方法的形参上,然后就可以在controller中使用该参数了 下面通过5个常用的注解演示下如何进行参数绑定: 1. @PathVariable注解 @PathVariabl

  • Spring MVC请求参数接收的全面总结教程

    前提 在日常使用SpringMVC进行开发的时候,有可能遇到前端各种类型的请求参数,这里做一次相对全面的总结.SpringMVC中处理控制器参数的接口是HandlerMethodArgumentResolver,此接口有众多子类,分别处理不同(注解类型)的参数,下面只列举几个子类: RequestParamMethodArgumentResolver:解析处理使用了@RequestParam注解的参数.MultipartFile类型参数和Simple类型(如long.int)参数. Reques

  • Spring MVC请求参数与响应结果全局加密和解密详解

    前提 前段时间在做一个对外的网关项目,涉及到加密和解密模块,这里详细分析解决方案和适用的场景.为了模拟真实的交互场景,先定制一下整个交互流程.第三方传输(包括请求和响应)数据报文包括三个部分: 1.timestamp,long类型,时间戳. 2.data,String类型,实际的业务请求数据转化成的Json字符串再进行加密得到的密文. 3.sign,签名,生成规则算法伪代码是SHA-256(data=xxx&timestamp=11111),防篡改. 为了简单起见,加密和解密采用AES,对称秘钥

  • Spring Cloud使用Feign实现Form表单提交的示例

    之前,笔者写了<使用Spring Cloud Feign上传文件>.近日,有同事在对接遗留的Struts古董系统,需要使用Feign实现Form表单提交.其实步骤大同小异,本文附上步骤,算是对之前那篇的补充. 添加依赖: <dependency> <groupId>io.github.openfeign.form</groupId> <artifactId>feign-form</artifactId> <version>

  • 浅谈Spring的两种事务定义方式

    一.声明式 这种方法不需要对原有的业务做任何修改,通过在XML文件中定义需要拦截方法的匹配即可完成配置,要求是,业务处理中的方法的命名要有规律,比如setXxx,xxxUpdate等等.详细配置如下: <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="

  • 猜你不知道Spring Boot的几种部署方式(小结)

    引言 本文主要讲的是spring boot的五种部署方式,里面是否有你不知道的呢,如果有欢迎评论留言哦,一起交流探讨哦!!! 可以使用各种方法将Spring Boot应用程序部署到生产系统中.在本文中,我们将通过以下5种方法逐步部署Spring Boot应用程序: 在Java Archive(JAR)中作为独立应用程序进行部署, 将Web应用程序存档(WAR)部署到servlet容器中, 在Docker Container中部署, 在NGINX Web服务器后面部署 - 直接设置, 部署在NGI

  • spring装配bean的3种方式总结

    前言 这段时间在学习Spring,依赖注入DI和面向切面编程AOP是Spring框架最核心的部分.这次主要是总结依赖注入的bean的装配方式. 什么是依赖注入呢?也可以称为控制反转,简单的来说,一般完成稍微复杂的业务逻辑,可能需要多个类,会出现有些类要引用其他类的实例,也可以称为依赖其他类.传统的方法就是直接引用那个类对象作为自己的一个属性,但如果我们每次创建这个类的对象时,都会创建依赖的类的对象,还有如果那个类将来可能不用了,还需要到这个类去删除这个对象,那破坏了代码的复用性和导致高度耦合!

  • Spring AOP的五种通知方式代码实例

    这篇文章主要介绍了Spring AOP的五种通知方式代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 AOP的五种通知方式: 前置通知:在我们执行目标方法之前运行(@Before) 后置通知:在我们目标方法运行结束之后,不管有没有异常(@After) 返回通知:在我们的目标方法正常返回值后运行(@AfterReturning) 异常通知:在我们的目标方法出现异常后运行(@AfterThrowing) 环绕通知:目标方法的调用由环绕通知决定

  • 详解Spring Boot使用Maven自定义打包方式

    前言:本文将告诉你如何将程序Jar与与依赖Jar及配置文件分离打包,以下列举了两种不同Maven打包方式,其打包效果一致! 一.第一种Maven打包方式,将jar及resources下全部配置文件,拷贝到指定目录: <!--配置项--><properties> <!--自定义配置--> <project.jar.output.directory>E:/IDEAFile/file-copy/target/project</project.jar.outp

  • spring cloud hystrix 超时时间使用方式详解

    我们在使用后台微服务的时候,各个服务之前会有很多请求和交叉业务.这里会引起雪崩.超时等异常处理.SpringCloud Hystrix服务降级.容错机治理使 hystrix 有很好的支持,引入后实现断路器功能. 1:pom 引入jar包 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</a

  • Spring整合MyBatis的三种方式

    1.整合之前的环境准备 导入相关的jar包 Junit测试 <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> MyBatis <dependency> <groupId

  • Spring IOC创建对象的两种方式

    IOC创建对象的方式 一. 使用无参构造创建对象(默认方式) 创建实体类 注意:属性必须要有set方法,来完成注入 public class User { private String name; public User() { System.out.println("执行了User类的无参构造方法~"); } public User(String name){ this.name = name; System.out.println("执行了User类的有参构造方法&quo

  • spring cloud gateway使用 uri: lb://方式配置时,服务名的特殊要求

    在gateway中配置uri配置有三种方式,包括 第一种:ws(websocket)方式: uri: ws://localhost:9000 第二种:http方式: uri: http://localhost:8130/ 第三种:lb(注册中心中服务名字)方式: uri: lb://brilliance-consumer 其中ws和http方式不容易出错,因为http格式比较固定,但是lb方式比较灵活自由.不考虑网关,只考虑服务时,服务名命名时比较自由,都能启动被访问,被注册到注册中心,但是如果

  • Spring cloud 限流的多种方式

    在频繁的网络请求时,服务有时候也会受到很大的压力,尤其是那种网络攻击,非法的.这样的情形有时候需要作一些限制.例如:限制对方的请求,这种限制可以有几个依据:请求IP.用户唯一标识.请求的接口地址等等. 当前限流的方式也很多:Spring cloud 中在网关本身自带限流的一些功能,基于 redis 来做的.同时,阿里也开源了一款:限流神器 Sentinel.今天我们主要围绕这两块来实战微服务的限流机制. 首先讲 Spring cloud 原生的限流功能,因为限流可以是对每个服务进行限流,也可以对

随机推荐