HTTP 协议是一种基于 TCP 的纯文本协议,一个基本的 HTTP 协议交互过程如下:
使用 nc 命令演示以及 mock 最基本的 http server 交互响应过程:
终端 1:
nc -kl 8080
终端 2:
curl -v localhost:8080
终端 1 结果类似如下:
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.75.0
Accept: */*
此时,我们在终端 1 手工输入一个模拟 http server 的响应:
HTTP/1.1 200 OK
Hello
此时,在终端 2 应该看到类似于下面的响应:
< HTTP/1.1 200 OK
<
Hello
HTTP 的协议的请求内容格式如下:
GET / HTTP/1.1 # <======== 第一行为 method location protocol
Host: localhost:8080 ######################
User-Agent: curl/7.75.0 ## 一些可选的 headers
Accept: */* ######################
# 一个空行
{body} # 可选的 request body
HTTP 协议的响应内容格式如下:
HTTP/1.0 200 OK # 第一行固定 protocol code description
Server: SimpleHTTP/0.6 Python/3.9.1 ################
Date: Sun, 14 Feb 2021 09:24:59 GMT ## 一些可选的 headers
Content-type: text/html; charset=utf-8 ##
Content-Length: 496 ################
# 一个空行
{body} # 响应正文内容
通常描述 REST 接口的文档中,直接省略请求过程以及非关键的 Header 部分,贴出关键部分的请求命令和 header,以及 body 部分,以一个登录 API 为例,可能看到的接口文档描述如下:
客户端请求:
POST /login
Content-Type: application/json
{"usernamae":"u1","password":"p1"}
服务端响应:
200 OK
Content-Type: application/json
{"token":"a1b2c3..."}
常见 HTTP Code 总结
1xx
: 信息响应100 Continue
,这个临时响应表明,迄今为止的所有内容都是可行的,客户端应该继续请求,如果已经完成,则忽略它。101 Switching Protocol
,该代码是响应客户端的 Upgrade 标头发送的,并且指示服务器也正在切换的协议。2xx
:成功响应200 OK
201 Created
204 No Content
206 Partial Content
,部分成功,断点续传必备3xx
:重定向301 Moved Permanently
,注意所有的请求都将被定向为 GET302 Found
,与 301 最直观的区别是 302 请求不会被搜索引擎收录304 Not Modified
,见下文缓存部分307 Temporary Redirect
,与 302 唯一的区别在于 method 保持原样308 Permanent Redirect
,与 301 唯一的区别在于 method 保持原样4xx
: 客户端错误400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
405 Method Not Allowed
5xx
: 服务端失败500 Internal Server Error
501 Not Implemented
502 Bad Gateway
,常见于反向代理503 Service Unavailable
,常见于云加速服务504 Gateway Timeout
,常见于反向代理参考资料: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
HTTP/1.0 增加以下内容:
HTTP/1.1 增加以下内容:
keepalive
支持cache-control
头)Accept-*
头)Host
头,以便支持虚拟主机HTTP/2 主要改进:
HTTP/3(QUIC)主要改进(目前仍是草案状态):
参考资料:
Host
头)通过 client 添加 Host 头(通常不需要用户干预,命令部分和 Host 头部分通常客户端会自动处理)以及服务端响应 Host 头,可以实现同一个服务器上提供多个网站的的场景。例:
GET / HTTP/1.1
Host: localhost:8080
内容协商的方式为客户端请求带上 Accept 类的头(尽管 User-Agent
不是标准的内容协商内容,但是实际开发中很多人还是会使用 User-Agent
作为协商依据),服务端会根据这个头响应客户端期望的内容以及对应的 header(header 也可能没有,而是直接返回对应期望的内容或者重定向到目标页面)。
常见的 Accept 对与示例:
客户端 Request | 服务端 Response(可能没有) |
---|---|
accept: application/json, text/html | content-type: application/json |
accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7 | Content-Language: zh-CN |
accept-encoding: br, gzip, deflete | content-encoding: gzip Vary: Accept-Encoding |
Vary
会影响下级服务端或用户浏览器的缓存策略。vary
头表示服务端基于哪个请求头做了内容协商(可能没有),再简单一点就是因为什么头的内容不同而响应内容不同。Vary
头可以防止缓存错乱,但是滥用会导致缓存命中率下降,因此通常不推荐 Vary: *
。实际使用常见的 Vary: Accept-Encoding
,User-Agent
,Origin
原理和过程部分强烈建议阅读 MDN 文档: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
客户端请求头:
OPTIONS /resource/foo # fetch 请求通常使用 OPTIONS 命令
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org
服务端期望的响应头:
HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: https://foo.bar.org
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400
打开链接下载文件需要满足以下二选一:
content-type
非浏览器能直接支持的,或者为默认的 application/octet-stream
Content-Disposition: attachment
,无论 content-type
是什么,都将变为文件下载,如果 Content-Disposition
的值包含 filename=for.bar
,则默认下载文件名为指定 filename
的值。例:Content-Disposition: attachment;filename=download.pdf
响应头中包含 Content-Length
,则客户端可以根据这个文件长度提醒下载进度
服务端必须支持 Accept-Ranges
响应,大部分情况下这个值是 bytes
:
Accept-Ranges: bytes
客户端请求带上 Range:(unit=first byte pos)-[last byte pos]
,如:
Range: bytes=0-499
服务端应该返回:
206 Partial Content
Content-Range: bytes 0-1023/146515
对于大容量数据、动态数据、流式数据这种不可提前预知容量(content-length
)的内容,应当采用 chunked 方式。它可以方便处理动态内容,以及动态维持客户端链接。客户端通常会对 chunked 进行流式处理。一个典型的 chunked 响应如下:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
7\r\n
Mozilla\r\n
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n
\r\n
具体的 chunked 部分格式如下:
[chunk size][\r\n][chunk data][\r\n][chunk size][\r\n][chunk data][\r\n][chunk size = 0][\r\n][\r\n]
HTTP 的缓存可以针对浏览器,也可以针对中间的代理层(如 CDN 等)。Vary
头会影响缓存效果(下不赘述)。
HTTP 协议的缓存结构可以参考下图(来自: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ ):
更灵活的本地缓存策略可以考虑上 service worker: https://developers.google.cn/web/ilt/pwa/caching-files-with-service-worker
哪些响应可以被缓存:
GET
可以被浏览器缓存,而 OPTIONS
和 HEAD
可以被 CDN 缓存(可能需要配置)常见缓存头:
Expires
(Since HTTP/1.0)
Expires: <http-date>
: Expires: Thu, 01 Dec 1994 16:00:00 GMT
Last-Modified
/ If-Modified-Since
(Since HTTP/1.0)
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
304 Not Modified
Etag
/ If-Non-Match
(Since HTTP/1.1)
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Non-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"
304 Not Modified
Cache-Control
(Since HTTP/1.1)
cache-control: max-age=3600,public
max-age
字段表示自获取资源之后,在本地缓存多久(单位:秒)public
/private
代表这个是公共还是私有缓存,公共缓存是可以被所有中间层缓存的,私有缓存只能在本地浏览器中缓存。包含 max-age
参数或包含 Expires 头的时候,默认为 public
,否则默认 private
cache-control
头可以同时包含在请求头与响应头中如果以上头在响应中都包含,并且客户端均支持,那么优先级如下: Cache-Control
> Expires
> Etag
> Last-Modified
缓存控制
Cache-Control
(Since HTTP/1.1)
no-store
:不使用任何缓存,每次重新发起请求,下载最新资源no-cache
:每次重新验证,如果服务端未更新返回 304 Not ModifiedPragma: no-cache
(Since HTTP/1.0)
cache-control: no-cache
效果相同cache-control
,只是向后兼容 HTTP/1.0 的缓存层和客户端缓存 FAQ
浏览器直接地址栏输入和 F5 以及 CTRL+F5 有什么区别?
如何判断浏览器/CDN 的缓存资源是否过期?
Age: <seconds>
头表示资源在服务器上缓存了多久(单位:秒),可以根据这个头判断最近资源是否有更新Last-Modified
头判断如何判断响应后端是真实的服务端还是 CDN?
Server
头判断。绝大多数自建服务器的 Server
可能为 Apache
, Nginx
, Tomcat
之类的,而 CDN 通常不直接使用这些 ServerVia
头,可以通过这个头的内容判断是否来自于 CDN,以及使用哪个 CDN如何判断是否命中 CDN 的缓存?
x-****
,这些信息用来帮助用户调试 CDN。多数 CDN 厂商会把是否命中缓存放在 x-cache
这个头。如阿里云 CDN:x-cache: HIT TCP_MEM_HIT dirn:11:115947616
,Cloudfront: x-cache: Hit from cloudfront
参考资料: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ
强制 HTTPS 通常有两种方案:重定向和 HSTS
。
HSTS
(HTTP Strict Transport Security
)是一个特殊的 http header: Strict-Transport-Security: max-age=<expire-time>
,如:
Strict-Transport-Security: max-age=31536000;includeSubDomains
用于告诉浏览器必须使用 HTTPS 访问指定资源。二者比对如下:
重定向(浏览器通常有上限次数限定) | HSTS | |
---|---|---|
适用性 | 所有客户端(必须开启 follow redirect) | 仅现代化的浏览器 |
强制性 | 强制。不跳转到 Location 无法获取资源 | 非强制。旧的 URL 依旧可用 |
额外请求 | 多一次服务端请求 | 内部重定向,无额外请求 |
非标准端口 | 支持 | 不支持 |
参考资料: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/HTTP_Strict_Transport_Security
HTTP/1.1 协议可以通过升级方式使用基于 HTTP/1.1 的高级协议,如 Websocket
和 WebDAV
等等。升级方式为添加以下两个头:
升级成功服务端应该返回 101 Switching Protocols
,并且之后的交互则使用高级协议规范进行交互。如果服务端不支持该升级协议,则应该返回 200 OK
,之后由客户端继续按照 HTTP 协议降级处理。
图片防盗链一种比较简单的防护策略是通过 Referer
头进行防护。假想你的网页代码中引入图片的部分如下:
<img src="pic.jpg" />
那么浏览器请求这个图片资源的时候,通常还会附带上当前的地址栏到 Referer
头,请求示例如下:
GET /pic.jpg HTTP/1.1
Host: localhost:8080
referer: http://localhost:8080/
服务端可以通过设置 Referer
头白名单的方式一定程度上实现防盗链。
觉得有帮助的话,不妨考虑购买付费文章来支持我们 🙂 :
付费文章