一、前言
http协议,应该是前端开发同学最熟悉的网络协议。在日常开发过程中,各种状态码、请求头、响应头常常在不经意间使你掉头,跨域请求、Cookie限制与安全(CSRF)、缓存问题,也一直伴随着几乎每一次面试。然而,这些只是http的冰山一角。http协议中,有许多一直在被使用,你却可能从未了解过的内容。
注意:以下关于http标准的内容是大部分浏览器或常见客户端支持的,标准和实现可以是完全不同的,鼓励大家遵循标准,但是很多时候反模式还是挺香的。
二、Form表单
ajax技术从发明开始,一直用一直爽。但在某些极致的场景下,如在2G弱网条件下的活动页,基本没必要使用任何js,一个html加一些内联样式,就可以解决。
2.1 Content-Type
- text/plain
对空格使用+号编码,其他字符不编码
- application/x-www-form-urlencoded
对所有字符编码
- multipart/form-data
上传文件时必须选择form-data(base64上传的当我没说),不同字段用boundary=--- xxx隔开,boundary可以自定义,在content-type中指定
POST /foo HTTP/1.1
Content-Length: 68137
Content-Type: multipart/form-data; boundary=---------------------------974767299852498929531610575
---------------------------974767299852498929531610575
Content-Disposition: form-data; name="description"
some text
---------------------------974767299852498929531610575
Content-Disposition: form-data; name="myFile"; filename="foo.txt"
Content-Type: text/plain
(content of the uploaded file foo.txt)
---------------------------974767299852498929531610575
问题:为什么要使用Content-Type: application/json
三、CORS跨域
3.1 简单请求
某些请求不会触发 CORS 预检请求 。本文称这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求满足所有下述条件,则该请求可视为“简单请求”:
-
使用下列方法之一:
-
GET
-
HEAD
-
POST
-
-
除了被用户代理自动设置的首部字段(例如 Connection , User-Agent )和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合 。该集合为:
-
Accept
-
Accept-Language
-
Content-Language
-
Content-Type (需要注意额外的限制)
-
DPR
-
Downlink
-
Save-Data
-
Viewport-Width
-
Width
-
-
Content-Type 的值仅限于下列三者之一:
-
text/plain
-
multipart/form-data
-
application/x-www-form-urlencoded
-
-
请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
-
请求中没有使用 ReadableStream 对象。
3.2 预检请求
-
Access-Control-Allow-Credentials: 控制是否允许携带Cookie
-
Access-Control-Allow-Origin:支持的来源
-
Access-Control-Allow-Method: 支持方法
-
Access-Control-Allow-Headers: 支持的请求头
-
Access-Control-Max-Age: 预检有效时间
问题:如何减少或者避免options预检请求?
四、协商缓存(Cache-Control: no-cache)
4.1 If-Modified-Since
只用于GET和HEAD请求,时间和文件的last-modified不一样,返回200和文件内容
curl 'http://127.0.0.1:2048/modify.html' -H 'If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT'
时间和文件的last-modified一致,返回304
curl 'http://127.0.0.1:2048/modify.html' -H 'If-Modified-Since: Tue, 24 Nov 2020 12:21:59 GMT'
如果同时存在If-None-Match,If-Modified-Since会被忽略
4.2 If-None-Match
用于GET和HEAD请求时,后面接的值是一个或多个Etag,当Etag与线上文件匹配上,返回304,否则返回200和文件内容
// 304
curl -I http://127.0.0.1:2048/modify.html -H 'If-None-Match: "5fbcfae7-263", "5fbcfae7-264"'
Etag值为*号时,可以用于判断文件是否存在,文件存在时返回304,不存在返回404
// 文件存在,上传失败,返回304
curl -X PUT -v -F 'file=@/Users/mooncat/http/modify.html;filename=modify.html;type=application/octet-stream' http://127.0.0.1:2048/temp -H 'If-None-Match: "*"'
// 文件不存在,上传成功,返回200
curl -X PUT -v -F 'file=@/Users/mooncat/http/modify.html;filename=modify.html;type=application/octet-stream' http://127.0.0.1:2048/temp -H 'If-None-Match: "*"'
如果是不安全的请求方法(如POST),Etag不配置时,返回412(Precondition Failed)
4.3 If-UnModified-Since
- 并发控制
作用:与不安全的请求方法配置,控制并发,确保文件未被修改,如果文件已经被修改,则返回412(Precondition Failed) 错误 应用场景: 编辑线上文件,但发布文件已经发生变化
- 断点续传
与Range请求头配合使用,获取文件内容时,如果文件发生变化,返回412(Precondition Failed) 错误 应用场景:文件下载过程中,线上文件发生修改
// 412 Precondition Failed
curl 'http://127.0.0.1:2048/modify.html' -H 'If-Unmodified-Since: Sun, 12th Nov 2020 12:12:12 GMT'
// 200 ok
curl 'http://127.0.0.1:2048/modify.html' -H 'If-Unmodified-Since: Tue, 24 Nov 2020 22:21:59 GMT'
4.4 If-Match
- 并发控制
作用:与不安全的请求方法配置,控制并发,确保文件未被修改,如果文件已经被修改,则返回
- 与Range配合使用,如果发现not match的情况,返回412(Precondition Failed)
4.5 Etag
Etag一般作为线上文件指纹
- W/ 可选
'W/' (大小写敏感) 表示使用 弱验证器 。弱验证器很容易生成,但不利于比较。强验证器是比较的理想选择,但很难有效地生成。相同资源的两个弱 Etag 值可能语义等同,但不是每个字节都相同。
- "<etag_value>"
实体标签唯一地表示所请求的资源。它们是位于双引号之间的ASCII字符串(如“675af34563dc-tr34”)。没有明确指定生成ETag值的方法。通常,使用内容的散列,最后修改时间戳的哈希值,或简单地使用版本号。例如,MDN使用wiki内容的十六进制数字的哈希值。
- Nginx的Etag生成规则
> curl -I http://127.0.0.1:2048/
HTTP/1.1 200 OK
Server: nginx/1.19.0
Date: Wed, 25 Nov 2020 15:46:35 GMT
Content-Type: text/html
Content-Length: 1871
Last-Modified: Wed, 25 Nov 2020 15:32:19 GMT
Connection: keep-alive
ETag: "5fbe7903-74f"
Accept-Ranges: bytes
问题:Etag怎么生成?
五、断点续传
http协议支持断点续传,前提是客户端和服务端都支持,任何一端不支持,都会引起全量传输,在某些浏览器上可能会导致一些异常(如音视频无法播放)
5.1 请求或响应头
- Accept-Range: none/bytes
none时,浏览器会禁止接收bytes
- Content-Range
Content-Range: <unit> <range-start>-<range-end>/<size>
Content-Range: <unit> <range-start>-<range-end>/*
Content-Range: <unit> */<size>
- Range
Http协议通过Range请求头控制续传的范围
$ curl -v 'http://127.0.0.1:2048/' -H 'Range: bytes=0-3'
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 2048 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:2048
> User-Agent: curl/7.64.1
> Accept: */*
> Range: bytes=0-3
>
< HTTP/1.1 206 Partial Content
< Server: nginx/1.19.0
< Date: Wed, 25 Nov 2020 15:59:19 GMT
< Content-Type: text/html
< Content-Length: 4
< Last-Modified: Wed, 25 Nov 2020 15:32:19 GMT
< Connection: keep-alive
< ETag: "5fbe7903-74f"
< Content-Range: bytes 0-3/1871
<
* Connection #0 to host 127.0.0.1 left intact
<!DO* Closing connection 0
Range的范围也可以是多个片断如,多个片断时用boundary隔离
curl -v 'http://127.0.0.1:2048/' -H 'Range: bytes=0-3, 8-9'
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 2048 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:2048
> User-Agent: curl/7.64.1
> Accept: */*
> Range: bytes=0-3, 8-9
>
< HTTP/1.1 206 Partial Content
< Server: nginx/1.19.0
< Date: Wed, 25 Nov 2020 16:00:27 GMT
< Content-Type: multipart/byteranges; boundary=00000000000000000032
< Content-Length: 202
< Last-Modified: Wed, 25 Nov 2020 15:32:19 GMT
< Connection: keep-alive
< ETag: "5fbe7903-74f"
<
--00000000000000000032
Content-Type: text/html
Content-Range: bytes 0-3/1871
<!DO
--00000000000000000032
Content-Type: text/html
Content-Range: bytes 8-9/1871
E
--00000000000000000032--
* Connection #0 to host 127.0.0.1 left intact
* Closing connection 0
5.2 状态码
-
206 Partial Content
-
416 Range Not Satisfiable
协议常用套路
提一个问题:http协议如何判断内容传输完成?
六、跳转的江湖:301、302、303、307、308
6.1 301 Moved Permanently
建议用在Get和Head方法,其他方法在某些浏览器301后方法会被改变,如:Post方法, 301后会变Get
6.2 302 Found
建议用在Get和Head方法,301一样,方法会被改变, Set-Cookie大部分浏览器会被传递到到reidirect后的地址,这一特性可能用来传递很多不可描述的参数,举个栗子,在微信登陆认证302跳转时,所有的参数都会被截断,这时候是可以通过set-cookie把参数写入到客户端,跳回来时,还是可以拿到的。
6.3 303 See Other
post改变成get方法,这是个feature
6.4 307 Temporary Redirect
修复302跳转时,请求方法被改变的问题
6.5 308 Permanent Redirect
修复301跳转时,请求方法被改变的问题
问题:304状态码在什么场景下发生?强缓存什么状态码?
七、代理、隧道有啥不同
-
正向代理
- 同样是通过第三方代理,隐藏访问来源,如通过代理,访问某些小网站
-
反向代理
- 通过第三方代理请求,隐藏背后真实的服务,前端常用于解决域名问题
-
隧道
- 在两端加上编码或协议转换工具,工具之前自由传输,到端后,再解码出来
CONNECT realserver.com:443 HTTP/1.0
User-Agent: GoProxy
八、总结
http协议的设计,非常地灵活,拓展性非常好,虽然很多时间有些修修补补的迹象。由于浏览器之间的竞争,其实标准本身对实现没有强约束,有很多怪异行为,这一点没有IP/TCP协议那么严格(虽然IP/TCP协议也有不少过度设计,计划赶不上变化)。
参考资料:CORS简单请求
-END-
●●●
●●●