vlambda博客
学习文章列表

http协议经常被忽略的知识

一、前言

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

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协议经常被忽略的知识

八、总结

http协议的设计,非常地灵活,拓展性非常好,虽然很多时间有些修修补补的迹象。由于浏览器之间的竞争,其实标准本身对实现没有强约束,有很多怪异行为,这一点没有IP/TCP协议那么严格(虽然IP/TCP协议也有不少过度设计,计划赶不上变化)。

参考资料:CORS简单请求


-END-

http协议经常被忽略的知识


喜欢我们的文章就关注吧~
每周新鲜前端好文推送


http协议经常被忽略的知识
觉得内容还不错的话,给我点个“在看”呗