0x00 前言
初学 Web 编程的时候发现想通过 HTML 表单上传文件的时候发现只需要简单的设置 form
标签的 enctype
属性为 multipart/form-data
便能成功的实现,后面才知道其实这是在设置表单内容的编码并且设置 HTTP
请求头的 Content-Type
属性。而在 W3C 标准里要求浏览器必须支持 application/x-www-form-urlencoded
和 multipart/form-data
这两种 enctype
方式,下面就来解释一下这两种类型吧!
0x01 application/x-www-form-urlencoded
注意:application/x-www-form-urlencoded 类型无法用于具有文件上传功能的表单。
浏览器 form
标签的 enctype
属性默认即为 application/x-www-form-urlencoded
,从字面上看我们看到了一个 urlencoded
。猜测是不是和 URL
相关?没错其实这种方式就是把我们平常用的 GET
方法所提交的参数放在了 HTTP
请求的 body
里,下面是一个通过 URL
传参例子:
1 | http://127.0.0.1:8000/?keyword=panda&author=zane |
可以看见重点在于 ?
号后面的部分,这部分指定了传递的参数的键和值。然后我们看一下通过 Postman 指定 Content-Type
为 application/x-www-form-urlencoded
所发送的 POST
请求报文:
1 | POST HTTP/1.1 |
通过比较,发现请求体中的内容和URL
中 ?
后的内容相等。
注意:和 URL 编码一致,当内容出现非 ASCII 字符时会被编码为 %HH 的形式(H是十六进制数的意思⊙o⊙)。
现在我们来点更详细的,将前面 URL
对应的 GET
请求报文贴出来:
1 | GET /?keyword=panda&author=zane HTTP/1.1 |
可以看见 HTTP
请求的 body
部分为空,所传递的参数在第一行(请求行)的 URL
里。所以当有人问你 HTTP
中 GET
和 POST
的区别的时候,你就可以告诉他「GET
将请求数据放在 URL
里, POST
将请求数据放在请求体里」。
偏题内容:为何说 GET 方法能提交数据要比 POST 少?
我在了解了 TCP
协议后便觉得其实 GET
和 POST
并无区别,因为对于 TCP
协议来说都只是数据而已。站在传输层的角度来说 URL
应该可以无限长,所以使用 GET
能提交数据不应该比 POST
少。但事实真的如此吗?稍微作个实验就知道是错的,因为这个限制出自「浏览器」和「 Web 服务器」。从浏览器的角度考虑,允许输入一个无限长文本的地址栏很明显就是一个错误的设计。此外在服务器的角度来说,因为 URL
在 HTTP
报文的第一行,如果允许 URL
无限长就意味着要等待 URL
全部接收完毕才可以获取其它头信息和请求体,对于一些需要验证某些头字段后才允许访问页面来说无疑带来了极大的资源的浪费(比如说接收到了一个很长的 URL
,但是后面的请求头的字段内容错误或者干脆就是不符合 HTTP
格式,这样前面接收 URL
所耗费的资源不都统统浪费了吗? ),这样不限制 URL
长度便容易被人所以利用攻击。因此大多数的「浏览器」和「 Web 服务器」都有限制 URL
长度,所以说在提交表单 POST
还是比 GET
好滴O(∩_∩)O!如果想了解更多的内容可以在 StackOverflow 上看这个问题 What is the maximum length of a URL in different browsers?,本人技术浅薄不敢多言(ㄒoㄒ)。
0x02 multipart/form-data
multipart/form-data
比 application/x-www-form-urlencoded
要复杂,和上面提交同样的数据,报文如下:
1 | POST / HTTP/1.1 |
首先我们看请求头的 Content-Type
它除了正常的 multipart/form-data
外还多了一个 boundary
,这个 boundary
的意思和字面意思一样就是分界线,通过分界线将每个键值对用 boundary
分割开来以示区别。现在我们看请求体,我们注意到boundary
将键值对分割后的每一部分都有 Content-Disposition
字段,实际上该字段的值必须为 form-data
而且后面必须加上 name
指定这部分的键名,然后是一行空行,空行之后便是提交数据的内容。 之所以要弄的这么复杂是因为 multipart/form-data
要支持文件上传。下面是一个包含文件上传的示例报文:
1 | POST / HTTP/1.1 |
得益于分界线使每部分内容都可以分开,所以文件的内容可以直接以二进制传输而不用经过编码。另外上传文件应该通过 filename
指定文件名并且通过 Content-Type
指定文件的 MIME 类型。
PHP 关于 multipart/form-data 的坑
注意 PHP 默认只解析 POST
方法的 multipart/form-data
请求(就是只有发送 POST
请求时才能获取到 multipart/form-data
提交的内容),也就说如果用其它的方法如 PUT
、PATCH
和 DELETE
发送 multipart/form-data
类型的请求,PHP 默认不会解析所以获取不到内容(当然大神可以自己通过 php://input
解析内容),但 application/x-www-form-urlencoded
是都可以用的。遇到这个坑是因为在使用 Postman 调试 API 时发现请求的参数怎么都获取不到,结果发现 Postman 选了 multipart/form-data
类型,然后请求方法是 PUT
。原来代码一切正常,却调了半天(ㄒoㄒ)……
0x03 总结
最后附上两条终极链接 application/x-www-form-urlencoded or multipart/form-data? HTML 表单之必知必会(手动狗头),O(∩_∩)O哈哈~~如果能看完就肯定会觉得我写的很垃圾了(ㄒoㄒ)。文章有误欢迎指出,欢迎友好讨论!