你所需要了解的关于http的一切
HTTP 报文结构
起始行 + 头部 + 空行 + 实体
- 起始行
请求报文格式是方法 + 路径 + http版本。
POST /home HTTP/1.1
返回报文格式起始行也叫做状态行,由http版本、状态码和原因三部分组成,
在起始行中,每两个部分之间用空格隔开,最后一个部分后面应该接一个换行。
HTTP/1.1 200 OK
-
头部
空行
用来区分开头部和实体。 如果说在头部中间故意加一个空行那么空行后的内容全部被视为实体。实体
具体的数据了,也就是body。请求报文对应请求体, 响应报文对应响应体。
HTTP 的请求方法
常用的有:
- GET: 通常用来获取资源
- HEAD: 获取资源的元信息
- POST: 提交数据,即上传数据
- PUT: 修改数据
- DELETE: 删除资源(几乎用不到)
- POST和GET差异
而后又有这样一些具体的差别:
- GET 请求会被浏览器缓存下来,留下历史记录,而 POST 默认不会。
- GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。
- GET 一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输隐私信息。
- GET 请求会把请求报文一次性发出去,而 POST 会分为两个 TCP 数据包,首先发 header 部分,如果服务器响应, 然后发 body 部分。
URI 结构
- scheme 协议名,比如http, https, file等等。后必须连://。
- user:passwd@ 表示登录主机时的用户信息,不常用。
- host:port表示主机名和端口。http 和 https 的默认端口分别为80、443
- path表示请求路径,标记资源所在位置。
- query表示查询参数,为key=val形式,多个键值对之间用&隔开。
- fragment表示 URI 所定位的资源内的一个锚点,浏览器可以根据这个锚点跳转到对应的位置。
https://www.baidu.com/s?wd=HTTP&rsv_spt=123
URI 中,https即scheme部分, www.baidu.com 为host:port部分(注意,),/s为path部分,而wd=HTTP&rsv_spt=1是query部分。
HTTP 状态码
RFC 规定 HTTP 的状态码为三位数,被分为五类:
1xx: 表示目前是协议处理的中间状态,还需要后续操作。在HTTP
升级为WebSocket
的时候,如果服务器同意变更,就会发送状态码 101。
2xx: 表示成功状态。
- 200 OK是见得最多的成功状态码。通常在响应体中放有数据。
- 204 No Content含义与 200 相同,但响应头后没有 body 数据。
3xx: 重定向状态,资源位置发生变动,需要重新请求。
301 Moved Permanently即永久重定向,
302 Found临时重定向。
比如网站 升级到了 HTTPS 了,以前的站点再也不用了,应当返回301
,浏览器默认会做缓存优化,在第二次访问的时候自动访问重定向的那个地址。
而如果只是暂时不可用,那么直接返回302
浏览器并不会做缓存优化。304 Not Modified: 当协商缓存命中时会返回这个状态码
4xx: 请求报文有误。
- 400 Bad Request: 简单返回错误。
- 403 Forbidden: 并不是请求报文出错,而是服务器禁止访问,比如法律禁止、信息敏感。
- 404 Not Found: 资源未找到,表示没在服务器上找到相应的资源。
- 405 Method Not Allowed: 请求方法不被服务器端允许。
- 406 Not Acceptable: 资源无法满足客户端的条件。
- 408 Request Timeout: 服务器等待了太长时间。
- 409 Conflict: 多个请求发生了冲突。
- 413 Request Entity Too Large: 请求体的数据过大。、
- 414 Request-URI Too Long: 请求行里的 URI 太大。
- 429 Too Many Request: 客户端发送的请求过多。
- 431 Request Header Fields Too Large请求头的字段内容太大。
5xx: 服务器端发生错误。
- 500 Internal Server Error: 服务器内部出错
- 501 Not Implemented: 表示客户端请求的功能还不支持。
- 502 Bad Gateway: 服务器自身是正常的,但访问的时候出错了,
- 503 Service Unavailable: 表示服务器当前很忙,暂时无法响应服务。
HTTP 的特点
- 灵活可扩展,主要体现在两个方面。一是语义上的自由,只规定了基本格式,其他的各个部分都没有严格的语法限制。另一个是传输形式的多样性,不仅仅可以传输文本,还能传输图片、视频等任意数据。
- 可靠传输。HTTP 基于 TCP/IP,因此把这一特性继承了下来。
- 请求-应答。也就是一发一收、有来有回,
- 无状态。每次 http 请求都是独立、无关的,默认不需要保留状态信息。
有些应用仅仅只是为了获取一些数据,不需要保存连接上下文信息,无状态减少了网络开销。
HTTP 缺点
- 无状态
在长连接的场景中,需要保存大量的上下文信息,以免传输大量重复的信息。 - 明文传输
协议里的报文(主要指的是头部)不使用二进制数据,而是文本形式。
调用的便利缺使 HTTP 的报文信息暴露给了外界,给攻击者也提供了便利。 - 队头阻塞
当 http 开启长连接时,共用一个 TCP 连接,同一时刻只能处理一个请求,那么当前请求耗时过长的情况下,其它的请求只能处于阻塞状态
HTTP压缩方式
对数据进行编码压缩的,压缩方式就体现在了发送方的Content-Encoding字段上, 同样的,接收什么样的压缩方式体现在了接受方的Accept-Encoding字段上。
gzip: 当今最流行的压缩格式
deflate: 另外一种著名的压缩格式
br: 一种专门为 HTTP 发明的压缩算法
// 发送端
Content-Encoding: gzip
// 接收端
Accept-Encoding: gizp
HTTP字符集
在接收端对应为Accept-Charset,指定可以接受的字符集,而在发送端并没有对应的Content-Charset, 而是直接放在了Content-Type中,以charset属性指定。
// 发送端
Content-Type: text/html; charset=utf-8
// 接收端
Accept-Charset: charset=utf-8
HTTP 对于定长和不定长的数据传输的
- 定长包体
对于定长包体而言,发送端在传输的时候一般会带上 Content-Length, 来指明包体的长度。如果设置不当可以直接导致传输失败。
就比如后端返回helloworld
如果后端设置res.setHeader('Content-Length', 7)
则前端只会收到hellowo
超出7位则被截取了。
- 不定长包体
通过 http 头部字段Transfer-Encoding: chunked
来表示分块传输数据,设置这个后Content-Length 字段会被忽略
并且会基于长连接持续推送动态内容。返回格式如下。
chunk长度(16进制的数)
第一个chunk的内容
chunk长度(16进制的数)
第二个chunk的内容
......
0
HTTP 处理大文件传输
对于大文件来说,一次性全部传输显然是不现实的,会有大量的等待时间,严重影响用户体验。因此,HTTP 采取了范围请求来解决允许客户端仅仅请求一个资源的一部分。要支持这个功能,就必须加上这样一个响应头:Accept-Ranges: none
来告知客户端这边是支持范围请求。
- Range 字段拆解
而对于客户端而言,它需要指定请求哪一部分,通过Range这个请求头字段确定,格式为bytes=x-y
。
服务器收到请求之后,首先验证范围是否合法,如果越界了那么返回416错误码,否则读取相应片段,返回206状态码。
同时,服务器需要添加Content-Range
字段,这个字段的格式根据请求头中Range字段的不同而有所差异。
// 单段数据
Range: bytes=0-9
// 多段数据
Range: bytes=0-9, 30-39
- 单段数据
对于单段数据的请求,返回的响应如下:
HTTP/1.1 206 Partial Content
Content-Length: 10
Accept-Ranges: bytes
Content-Range: bytes 0-9/100
Content-Range字段,0-9表示请求的返回,100表示资源的总大小,很好理解。
- 多段数据
接下来我们看看多段请求的情况。得到的响应会是下面这个形式:
HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=00000010101
Content-Length: 189
Connection: keep-alive
Accept-Ranges: bytes
--00000010101
Content-Type: text/plain
Content-Range: bytes 0-9/96
i am xxxxx
--00000010101
Content-Type: text/plain
Content-Range: bytes 20-29/96
eex jspy e
--00000010101--
Content-Type: multipart/byteranges;boundary=00000010101
,它代表了信息量是这样的:
- 请求一定是多段数据请求
- 响应体中的分隔符是 00000010101
在响应体中各段数据之间会由这里指定的分隔符分开,而且在最后的分隔末尾添上--表示结束。
HTTP 中如何处理表单数据的提交?
在 http 中,有两种主要的表单提交的方式,体现在两种不同的Content-Type取值:
- application/x-www-form-urlencoded
- 其中的数据会被编码成以&分隔的键值对
- 字符以URL编码方式编码。
// 转换过程 -> 如下(最终形式)
前: {a: 1, b: 2} -> a=1&b=2
后:"a%3D1%26b%3D2"
- multipart/form-data
- 请求头中的Content-Type字段会包含boundary,且boundary的值有浏览器默认指定。例:
Content-Type: multipart/form-data;boundary=----WebkitFormBoundaryRRJKeWfHPGrS4LKe
。
数据会分为多个部分,每两个部分之间通过分隔符来分隔。
相应的请求体是下面这样:
Content-Disposition: form-data;name="data1";
Content-Type: text/plain
data1
----WebkitFormBoundaryRRJKeWfHPGrS4LKe
Content-Disposition: form-data;name="data2";
Content-Type: text/plain
data2
----WebkitFormBoundaryRRJKeWfHPGrS4LKe--
- 每一个表单元素都是独立的资源表述。在平时的调试中,可能并没有注意到其中还有boundary的存在,之所以在平时感觉不到,是以为浏览器和 HTTP 给你封装了这一系列操作。
- 在实际的场景中,对于图片等文件的上传,基本采用multipart/form-data而不用application/x-www-form-urlencoded,因为没有必要做 URL 编码,带来巨大耗时的同时也占用了更多的空间。