http数据提交你不知道的事

    说到http数据提交,大家肯定都不陌生,web开发经常需要将数据提交到后台服务器处理,但http数据的详细处理过程了解的人估计就不多了。本文主要解释下面3个问题:

1、不同的http方法与Content-Type组合时,浏览器和ajax是如何处理

2、不同的http方法与Content-Type组合时,应用服务器是如何处理

3、Spring框架针对http方法做的特殊处理

一、HTTP请求数据结构

在解释http数据处理过程之前,我们先看看http数据传输的结构。如下图所示,HTTP请求消息由四个部分组成:请求行,请求头部、空行、请求数据。

请求数据示例:

其中,标红的3个部分是我们需要关注的。它们分别对应http请求方法、请求数据类型、请求数据。

HTTP请求方法

HTTP协议规定的HTTP请求方法有9种:GET、HEAD、POST、PUT、DELETE、OPTIONS、TRACE、CONNECT、PATCH。其中,前8个在HTTP1.1协议RFC 7231规范中定义,最后的PATCH是补充的RFC 5789规范中定义。

备注:

RFC 7231:https://tools.ietf.org/html/rfc7231

RFC 5789:https://tools.ietf.org/html/rfc5789

http数据提交,一般是通过form表单(支持get/post)提交或者直接把请求数据拼接在URL后面(get方式),所以我们用的比较多的是get/post这两种方式。对于HTTP规定的其他7个方法,大家都不怎么关注,有些人甚至以为只有get/post两种方式。

然而最近这些年,随着RESTful软件架构的兴起,大家开始关注和使用其他3个方法PUT/DELETE/PATCH。POST、DELETE、PUT/PATCH、GET这5个方法在语义上对应了数据的增、删、改、查。(详细的RESTful内容这里就不展开)

说明:http方法的幂等性指的是一次和多次发送请求,结果是一样的。这个结果一样,既指的是服务器返回的数据一样,也包括服务器上的资源状态一样。例如,使用Patch方法将某个用户的年龄修改为20岁。发起一次请求和发起多次请求,这个用户的年龄都是20岁。另外注意,http协议规定,delete的方法是幂等的,服务器在成功处理第一个delete请求时返回200状态码(删除成功),在处理第二次、第N次delete时,也要返回200状态码(删除成功),不应返回404状态码(资源不存在)。

Content-Type

ContentType用于指明http请求数据的MediaType媒体类型,也叫MIME类型。常见的媒体类型有:

二、html提交表单数据

     在web页面上,如果通过点击提交按钮或者使用js脚本触发form提交时,浏览器会自动对提交的Form表单数据进行格式转换处理,有3种处理方式,Query String、Form Data、Request Payload。

html的form表单属性

1、method属性:只支持GET/POST,默认GET。注意,如果是除GET/POST外的其他值,按默认值(GET)的方式处理

2、enctype属性:只支持application/x-www-form-urlencoded(默认)、multipart/form-data、text/plain,若为其他值,则使用默认值(application/x-www-form-urlencoded)的方式处理

Query String方式

浏览器会对数据先编码,然后按照“key1=value1&key2=value2” 键值对格式然后拼接成字符串,最后使用问号(?)连在URL后面。例如,

https://littleof.com/test?name=litao&pwd=123

说明:

Query String方式可以和其他方式一起使用,只要数据拼接在url地址上就行。例如下图例子,使用POST方法向http://localhost:8443/test?fullname=litao&sex=male地址发起请求。此时,可以看到一个请求中,同时有两种方式的请求数据。

关于URL编码:由于数据拼接在了URL地址上,就要遵循URL编码规范。URL地址采用的ASCII字符集,所以对于非ASCII中的字符(例如中文字符)就需要进行编码。URL编码采用的是2005年发布的RFC3986“%编码”(也叫百分号编码)。另外,空格使用+号代替。

Form Data方式

数据格式与Query String一样,浏览器会对数据先按照URL编码规则进行编码,然后按照“key1=value1&key2=value2”键值对格式拼接成字符串。但是拼接好的字符串,不是作为URL地址的一部分追加在URL地址后面,而是作为请求数据提交。

    只有在请求方法是POST,并且Content-Type属性为application/x-www-form-urlencoded时才使用这种方式提交数据。

Request Payload方式

不改变数据格式,直接发送。但是这里有个特殊情况--文件上传。

1、文件上传Payload

浏览器会在每条数据加上如下2个属性,然后再用分隔符(boundary)将这些数据拼接起来组成一条数据发送到服务器

属性1 Content-Disposition:包含3个参数,第一个固定form-data;第二个name,第三个filename(可选),示例:

Content-Disposition:form-data; name="fieldName" -->普通数据

Content-Disposition:form-data; name="fieldName"; filename="filename.jpg"-->文件数据

属性2 Content-Type:数据的类型。(可选)默认text/plain

分隔符(boundary)是由浏览器生成,每次都变化的。

只有在请求方法不是GET(即POST/PUT/PATCH),并且Content-Type属性为multipart/form-data时才使用这种方式提交数据。

2、普通Payload

数据直接提交到服务器。浏览器不会对数据进行转换处理。

当请求方法是不是GET,Content-Type属性不是multipart/form-data时使用这种方式提交数据。

总结一下,不同表单属性组合与处理方式的对应关系如下:

三、Ajax提交数据

Ajax提交数据时,浏览器不会对数据做格式转换处理,但需要注意如果你使用了前端框架,如jquery,框架可能会对发送的数据做转换处理。前端js脚本需要根据服务器接口的要求上送正确格式的数据和ContentType属性值(用于告知服务器上送数据的格式)。

注意:jquery的post/get方法,他们的ContentType固定是application/x-www-form-urlencoded,不能修改。

原生javascript发起ajax

使用原生ajax(即使用XMLHttpRequest对象)发起ajax时,默认的Content-Type是text/plain

jquery发起ajax

jquery提供了两组方法发起ajax请求,$.ajax() 和 $.get()/$.post()。需要重点关注这些方法的两个参数contentType和data。这两组方法都可能会对data数据进行转换处理。

1、$.ajax()

contentType默认是application/x-www-form-urlencoded。data支持3种格式的数据类型:json对象、string、限定格式数组。

限定格式数组要求数组中的对象包含一个或两个name/value对。例如:

jquery提供了方法serializeArray()快速得到form表单的限定格式数组。

发送时转换处理:

对于json对象和数组,会转换成query string键值对格式(即“key1=value1&key2=value2”);

对于string,则不做处理,直接发送。但如果方法名为get,则会将string直接拼接到URL后面,且会加上?号。这里注意,如果服务器是以参数方式接受请求数据,则使用string类型时,要拼成query string键值对格式。jquery提供了方法serialize()快速得到form表单的键值对格式。

从jquery的处理可以知道,如果需要发送son格式数据到服务器,data是不能传json对象的,因为jquery会将它转化成query string键值对格式。这种情况下,就要使用json的string字符串格式。可以手工拼接或者JSON.stringify(json)得到,例如:

注意:contentType要设置为application/json类型,否则使用会使用默认的application/x-www-form-urlencoded

2、$.post()/$.get()

这两个方法contentType固定是application/x-www-form-urlencoded。data支持2种类型:json对象和string。

发送时转换处理:

对于json对象,发送时转出query string键值对格式;对于string,则必须为键值对格式。由于contentType固定是application/x-www-form-urlencoded,服务器要以参数方式获取数据。示例:

$.post("test.php", { name: "John", time: "2pm" })

.done(function( data ) {

alert( "Data Loaded: " + data );

});

四、Java Web对http数据的处理

Java Web需要运行在Servlet容器中,常见的Servlet容器有:Tomcat、Websphere、WebLogic、JBoss、Jetty、Undertow。这些Servlet容器,也称为应用服务器,都要遵循Java的Servlet规范。规范主页:https://javaee.github.io/servlet-spec/

根据Servlet规范,Servlet容器收到客户端提交过来的http请求,要先进行解析处理,将请求数据解析成请求参数。请求参数可以通过ServletRequest接口的getParameter开头的方法(包括getParameter、getParameterNames、getParameterValues、getParameterMap)获取。这个解析处理过程是有条件的,对于不满足条件的请求数据,需要通过InputStream输入流获取。

从这可以看出,Servlet容器的解析过程,其实是为了简化http数据的处理过程。在实际开发过程中,我们基本都是使用ServletRequest.getParameter*()方法来获取请求数据,很少直接通过Inpustream流来获取。

1、请求参数解析条件

在Servlet规范中,规定了应用服务器对http数据的参数解析条件。以4.0版本为例(https://javaee.github.io/servlet-spec/downloads/servlet-4.0/servlet-4_0_FINAL.pdf),如下图所示,在规范的3.1章节,Servlet容器会从URI的Query String和POST方法数据中提取请求参数。

Query String 也就是前面提到的“key1=value1&key2=value2” 键值对格式。而POST方法数据不是都能转换成参数数据。满足4个条件(如下图所示),重点是第3个,即ContentType要为“application/x-www-form-urlencoded”。

总结:

对于“URI地址上的请求数据”和“提交方法为POST,且ContentType为application/x-www-form-urlencoded的请求数据”,Servlet容器会自动将他们解析成请求参数。

2、Spring处理http请求

在SpringBoot或Springmvc项目的启动日志中,我们可以看到,spring框架默认会在/*路径绑定4个过滤器。

其中,characterEncodingFilter用于设置码制;hiddenHttpMethodFilter和httpPutFormContentFilter对http方法做了特殊处理;requestContextFilter用于将请求转给Controller的方法处理。

HttpPutFormContentFilter

在RESTful风格的http接口中,GET/POST/PUT/PATCH/DELETE这5个方法都会经常用到。而前面说了,Servlet容器只会解析处理GET和POST方法发起的请求数据。基于这个原因,Spring提供了HttpPutFormContentFilter过滤器,它对Servlet的参数解析条件进行了扩展。

Servlet规范中,方法名是POST的数据才会被解析,Spring扩展支持了PUT/PATCH这两个方法。也就是说,如果你的项目使用的是SpringBoot或SpringMVC框架,则对于方法名为POST/PUT/PATCH,且ContentType为“application/x-www-form-urlencoded”的http请求数据,都会自动转换成请求参数。这点其实在HttpPutFormContentFilter源码的注释(如下图所示)中已经写得很清楚。

大家可能发现了,这个filter并没有扩展DELETE方法。个人觉得可能是因为在一般的业务场景中,删除操作不需要请求数据,都是直接通过唯一编号进行删除。如果确实需要上送请求数据,则可以通过在URI地址中拼接的方式送值。

Tomcat服务器中,可以通过修改server.xml配置文件的parseBodyMethods属性的值来扩展支持解析处理的方法。

connectionTimeout="20000"

redirectPort="8443"

parseBodyMethods="POST,PUT,PATCH"

URIEncoding="UTF-8" />

但是,非常不推荐这种方式,修改parseBodyMethods属性会违反Servlet规范。这点,在Tomcat的说明文档中(http://tomcat.apache.org/tomcat-7.0-doc/config/ajp.html)有提醒说明。违反规范的后果就是,你的程序如果换了一个环境运行就要同步修改这个值。

HiddenHttpMethodFilter

这个过滤器会检查POST请求参数中,是否有参数的参数名为“_method”,如果有,则会将这个参数的参数值作为http请求的方法名。也就是说它会修改http的请求方法名。

从过滤器的源码注释可以知道,增加这个注释的原因是因为浏览器只支持GET/POST两种方式,如果要发起PUT/PATCH/DELETE请求,则只能通过ajax的方式。有了这个过滤器后,只要在表单提交数据中加上name为“_method”的参数即可实现。

Spring的@RequestParam和@RequestBody

这两个注解都是用在Controller的方法参数上。

@RequestParam从请求参数中获取数据,你也可以省略这个注解,也就是说,Controller方法的参数默认从请求参数中获取值。

@RequestBody从InputStream流中获取数据,Spring框架会根据ContentType值选择对应的HttpMessageConverter消息转换器将InputStream流数据转换成参数对象。比较常见的是json。

文件上传

Servlet规范规定了ContentType为“multipart/formdata”时,当做文件上传数据进行处理。如下图所示:

Spring文件上传

Spring提供了MultipartFile接口用于接收请求数据中的上传文件,例如:

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180725G20HPN00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券