HTTP协议中,规定的HTTP请求方法大致有:
GET/POST/PUT/PATCH/DELETE/
COPY/HEAD/OPTIONS/LINK/UNLINK/PURGE/
LOCK/UNLOCK/PROPFIND/VIEW
其中我们最常用的有 GET/POST/PUT/PATCH/DELETE 这五种,而本文选取其中最典型的 POST 请求提交数据的方法进行探讨分析.
1.HTTP请求报文解剖
首先HTTP协议建立在 TCP/IP 协议之上,且以 ASCII 码传输信息内容.
HTTP 的规范将 HTTP 请求分为三个部分:状态行、请求头、消息主体.举例如下:
HTTP请求报文分析:
①是请求方法,GET和POST是最常见的HTTP方法.大多数浏览器只支持GET和POST,Spring 3.0提供了一个HiddenHttpMethodFilter,允许通过“_method”的表单参数指定这些特殊的HTTP方法(实际上还是通过POST提交表单).服务端配置了HiddenHttpMethodFilter后,Spring会根据_method参数指定的值模拟出相应的HTTP方法,这样,就可以使用这些HTTP方法对处理方法进行映射了.
②为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL.
③是协议名称及版本号.
④是HTTP的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息.
⑤是报文体,它将一个页面表单中的组件值通过param1=value1¶m2=value2的键值对形式编码成一个格式化串,它承载多个请求参数的数据.不但报文体可以传递请求参数,请求URL也可以通过类似于“/chapter15/user.html? param1=value1¶m2=value2”的方式传递请求参数.
1.1 常见的HTTP请求报文头属性
1.1.1 Accept
请求报文可通过一个“Accept”报文头属性告诉服务端,客户端接受什么类型的响应.
Accept:text/plain
1.1.2 Cookie
客户端的Cookie是通过这个报文头属性传给服务端.Cookie包含一些版本信息内容等,进而服务器可以识别请求是哪个版本的客户端发送来的请求等其他作用.
Cookie: $Version=1; Skin=new;jsessionid=5F4771183629C9834F8382E23BE13C4C
1.1.3 Referer
请求是从哪个URL过来的.
1.1.4 Cache-Control
缓存控制,一个请求希望响应返回的内容在客户端要被缓存多久,或不希望被缓存可以通过这个报文头达到目的.
Cache-Control: no-cache
以上是个人觉得比较常见和重要的报文头属性,还有其他内容暂不做介绍.
报文头属性参数以及数值,都是客户端对服务端发起的,服务端只能读.
2.HTTP响应报文解剖
HTTP 响应也分为三个部分:响应行、响应头、响应体.举例如下:
HTTP响应报文分析:
①报文协议及版本;
②状态码及状态描述;
③响应报文头,也是由多个属性组成;
④响应报文体,即客户端想要获取的请求内容.
2.1 响应状态码
和请求报文相比,响应报文多了一个“响应状态码”,它以“清晰明确”的语言告诉客户端本次请求的处理结果.
HTTP的响应状态码由5段组成:
- 1xx 消息,一般是告诉客户端,请求已经收到了,正在处理,别急...
- 2xx 处理成功,一般表示:请求收悉、我明白你要的、请求已受理、已经处理完成等信息.
- 3xx 重定向到其它地方.它让客户端再发起一个请求以完成整个处理.
- 4xx 处理发生错误,责任在客户端,如客户端的请求一个不存在的资源,客户端未被授权,禁止访问等.
- 5xx 处理发生错误,责任在服务端,如服务端抛出异常,路由出错,HTTP版本不支持等.
常见的几个状态码如下:
200 OK
303 See Other / 304 Not Modified
404 Not Found
500 Internal Server Error
2.2 常见的HTTP响应报文头属性
2.2.1 Cache-Control
响应输出到客户端后,服务端通过该报文头属告诉客户端如何控制响应内容的缓存.
Cache-Control: max-age=3600
2.2.2 ETag
一个代表响应服务端资源(如页面)版本的报文头属性,如果某个服务端资源发生变化了,这个ETag就会相应发生变化.它是Cache-Control的有益补充,可以让客户端“更智能”地处理什么时候要从服务端取资源,什么时候可以直接从缓存中返回响应.
ETag: "737060cd8c284d8af7ad3082f209582d"
2.2.3 Location
在JSP中让页面Redirect到一个某个A页面中,其实是让客户端再发一个请求到A页面,这个需要Redirect到的A页面的URL,其实就是通过响应报文头的Location属性告知客户端的,如下的报文头属性,将使客户端redirect到iteye的首页中:
Location: http://www.iteye.com
2.2.4 Set-Cookie
服务端可以设置客户端的Cookie,其原理就是通过这个响应报文头属性实现的:
Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
以上是个人觉得比较常见和重要的报文头属性,还有其他内容暂不做介绍.
3.关于HTTP请求的具体操作(构建请求体Body)
通过前面1和2的粗浅分析,可以知道在客户端和服务端数据通信过程中,能否正常通顺的请求到客户端想要的数据,对于客户端来说是客户端最关心的事情.
HTTP协议规定了POST请求所提交的数据必须要放到消息主体中,但协议并没有规定数据必须使用什么编码方式.所以在开发过程中,开发者完全可以自己决定消息体内容格式,最后只要HTTP请求发出去的内容满足HTTP的格式就行了.所以你们经常会看到有很多五花八门的HTTP格式.虽然很随意不过大多数开发者还会遵守一些默认的规定:服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析[注意:请求头和响应头都有Content-Type属性,它们分别都是用来约束各自的主体消息的,所以不要弄混].如果按照这种设定就意味着: POST 提交数据方案也同样包含了 Content-Type 和消息主体编码方式两部分内容.这二部分内容在一起其实就是构造整个请求体的标准和具体内容.
POSTMAN上给了四种常见的请求体类型(body类型):
form-data/x-www-form-urlencoded/row/binary
3.1 multipart/form-data (form-data)
使用表单上传文件时,必须让
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"
title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png
PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--
可以粗浅的分析下这个内容的结构:首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复, boundary 很长很复杂.然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容.消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 –boundary 开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制).如果传输的是文件,还要包含文件名和文件类型信息.消息主体最后以 –boundary– 标示结束.
所以以 –boundary 作为分割线分部分上传多种不同的内容的混合上传模式,最后再以 –boundary– 来结尾.
注意:
1.Content-Type:multipart/form-data
2.格式内容必须要 以 –boundary 标示开始 以 –boundary– 标示结束.
3.a.如果要上传KEY-VALUE:
Content-Disposition: form-data; name="KEY"
VALUE
这里的KEY就是键值对的KEY,换行之后(\r\n),再写VALUE.
b.如果上传图片或文件:
Content-Disposition: form-data; name="KEY"; filename="wfiegpwakg.jpg"
Content-Type: image/jpeg
c.其他上传文件与图片类似,不做赘述.
4.最后要以Data二进制流的形式上传.
这里有一个处理KEY-VALUE的代码范例,这里显得很不简洁,远没有设置参数的方法简单.如果单纯的只有普通数据提交,建议不要用 multipart/form-data 这种方式.
NSString *boundary = [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
NSMutableData *body = [NSMutableData data];
// 表单数据
NSMutableDictionary *param = [@{@"email":self->_email,@"type":self->_type,@"clientType":@"1"} mutableCopy];
[param enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSMutableString *fieldStr = [NSMutableString string];
[fieldStr appendString:[NSString stringWithFormat:@"–%@\r\n", boundary]];
[fieldStr appendString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key]];
[fieldStr appendString:[NSString stringWithFormat:@"%@", obj]];
[body appendData:[fieldStr dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 结束部分
NSString *bottomStr = [NSString stringWithFormat:@"-–%@–-", boundary];
/**拼装成格式:
-–Boundary+72D4CD655314C423–-
*/
[body appendData:[bottomStr dataUsingEncoding:NSUTF8StringEncoding]];
//[formData appendPartWithHeaders:@{@"Content-Type":[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary]} body:body];
//最后是用request来直接设置httpbody和content-type还是用AFMultipartFormData来设置,要看框架和具体实现,怎么方便怎么来.但是可以肯定的是,这里的httpBody肯定不能用params参数(字典的形式/或者序列化后的字典JSON字符串)来提交了.只能用方法给HttpBody复制Data.
//补充:为何iOS开发或者安卓开发中可以直接给HttpBody添加Params来传值,是因为框架底层都对Params进行了处理,让它成为Data,是给框架使用者提供了方便.
3.2 application/x-www-form-urlencoded (x-www-form-urlencoded)
绝大部分浏览器的原生form表单提交形式就是这种形式,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据.
1.Content-Type: application/x-www-form-urlencoded
2.提交的数据按照 key1=value1&key2=value2 的方式进行编码, key 和 value 都进行了 URL 转码,可以理解为一个string,但是是有&链接并且转码后的.
3.不支持文件类型数据的提交,只支持键值对的形式提交.
POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
3.3 application/json (row的一种类型)
这种就是最常见的POST请求提交的JSON数据.
1.Content-Type: application/json;charset=utf-8
2.JSON序列化后的string数据
POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8
{"title":"test","sub":[1,2,3]}
3.4 application/text/xml (row的一种类型)
这种就是最常见的POST请求提交的text/xml文本格式数据.
1.Content-Type: text/xml
2.text/xml标签格式的数据
(由于text/xml标签格式的数据书写麻烦,且JSON序列化的简单易用,导致这种方法逐渐被摒弃)
POST http://www.example.com HTTP/1.1
Content-Type: text/xml
<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value><i4>41</i4></value>
</param>
</params>
</methodCall>
3.5 其他内容 (row的其他类型 + binary类型)
1.上述的3.3和3.4的Content-type同属于row类型,row就是相当于给你一个文本框,你可以在里面写约定的固定格式的内容,然后把约定好的固定格式的内容提交,常见的如下几种:
Text
Text(text/plain)
JSON(application/json) —–> [对应上述3.3]
Javascript(application/javascript)
XML(application/xml)
XML(text/xml) —–> [对应上述3.4]
HTML(text/html)
其他几个内容大体上和3.3以及3.4相同,具体肯定也略有差异,在此不做赘述.
2.关于binary类型,其实相当于Content-Type:application/octet-stream,可以上传二进制数据,通常用来上传文件,由于没有键值,所以一次只能上传一个文件.它的特性决定了它使用不够普及,到目前为止我还没用过这种类型的上传方式.
4.综述
这篇文章的意义在于,以后跟服务端对接口的时候,直接问body体构建类型是form-data/x-www-form-urlencoded/row/binary这四种中的哪一种类型,既显得专业,又降低了交流成本.
从上面的内容可以看出,目前最通用话的应该是3.3这种类型,因为好处理,其他类型都在格式上有强烈的要求和限制,稍不注意就会犯拼写的格式错误,而提交不了数据,访问不了服务器接口.此外JSON的序列化和反序列化的普及,又方便了这里的数据通信,各大网络请求框架不论客户端网页端都毫无疑问都支持JSON序列化,从接口的通用性来说,大多数人还是会选择3.3这种类型.
其次就是3.2这种类型.此外3.2这种类型和GET请求有着很大的相似性,现在大部分的网络请求框架的处理表现形式会给我们在这点的区分上有很大的迷惑性,下面我会讲它们的具体差异.
3.2这种类型有点像GET请求在URL加?加key=value&key=value,但是有着本质上的区别,因为GET请求的?key=value&key=value是追加在URL后面的,而3.2是要放到Body体里的,对于GET请求而言,它没有真正意义上的Body体,通用的网络框架对于GET请求需要追加的参数虽然外表也用Params来接收外部的变量,但是内部处理还是追加在URL后的,所以3.2这种情况和GET请求有着本质上的差别,虽然外在形式可能很接近.