HTTP(超文本传输协议,英语:HyperText Transfer Protocol)是一个用于传输超媒体文档(例如 HTML)的应用层协议,是万维网的数据通信的基础。
版本
- 1999年6月公布的 RFC 2616,定义了 HTTP 协议中现今广泛使用的一个版本 HTTP 1.1。
- 2015年5月以 RFC 7540 正式发布 HTTP/2 标准,取代 HTTP 1.1 成为 HTTP 的实现标准。
- 2022年6月6日标准化为 RFC9114 的最新版本 HTTP/3,抛弃使用 TCP,通过 UDP 上使用 QUIC 来承载应用层数据。
通信过程
- 使用 TCP 协议,通过网页浏览器、网络爬虫或者其它的工具,客户端(user agent,用户代理程序)发起一个 HTTP 请求到服务器上指定端口(默认端口为80)。
- 应答的服务器(origin server)上存储着一些资源,比如 HTML 文件和图像,服务器在那个端口监听客户端的请求。在用户代理和源服务器中间可能存在多个“中间层”,比如代理服务器、网关或者隧道(tunnel)。
- 一旦收到请求,服务器会向客户端返回一个状态,比如"HTTP/1.1 200 OK",以及返回的内容,如请求的文件、错误消息、或者其它信息。
- 每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。HTTP/2 中的连接具有复用性,即每个目标地址建立连接后,可以永久被利用,所以每个来源仅需要一个连接。
请求方法
它们都实现了不同的语义,但根据共同的特征由可以分类为:safe(安全), idempotent(幂等), 或 cacheable(可缓存)。
- GET 的请求应该只被用于获取数据。
- HEAD 方法请求一个与 GET 请求的响应相同的响应,但没有响应体。
- POST 方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。
- PUT 方法用请求有效载荷替换目标资源的所有当前表示。
- DELETE 方法删除指定的资源。
- CONNECT 方法建立一个到由目标资源标识的服务器的隧道。
- OPTIONS 方法用于描述目标资源的通信选项。
- TRACE 方法沿着到目标资源的路径执行一个消息环回测试。
- PATCH 方法用于对资源应用部分修改。
Safe(安全)
- 指这是个不会修改服务器的数据的方法,也就是说,这是一个对服务器只读操作的方法。
- 浏览器调用安全的方法不用考虑会给服务端造成什么危害,这样,服务端就能允许客户端预加载资源。
- 这些方法是安全的:GET,HEAD 和 OPTIONS。所有安全的方法都是幂等的,但并非所有幂等方法都是安全的,例如,PUT 和 DELETE 都是幂等的,但不是安全的。
Idempotent(幂等)
- 指的是同样的请求被执行一次与连续执行多次,客户端接收到的结果都是一样的,服务器的状态也是一样的,也就是说,幂等方法不应该具有副作用(统计用途除外)。
- 在正确实现的条件下, GET、HEAD、OPTIONS、PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。所有的 safe 方法也都是幂等的。例如下面调用多次 POST 方法,就会增加多行记录:
bash
复制代码
POST /add_row HTTP/1.1
POST /add_row HTTP/1.1 -> Adds a 2nd row
POST /add_row HTTP/1.1 -> Adds a 3rd row
下面即使请求多次 DELETE 方法接收到的状态码不一样,但也是幂等的:
sql
复制代码
DELETE /idX/delete HTTP/1.1 -> Returns 200 if idX exists
DELETE /idX/delete HTTP/1.1 -> Returns 404 as it just got deleted
DELETE /idX/delete HTTP/1.1 -> Returns 404
Cacheable(可缓存)
- 可以被缓存的 HTTP 响应,将被存储以供以后检索和使用。
- 请求中使用的方法本身是可缓存的,即一个 GET 或一个 HEAD 方法。如果指示新鲜度并设置了 Content-Location 标头,也可以缓存对 POST 或 PATCH 请求的响应,但这很少实现。其他方法如 PUT 或 DELETE 不可缓存,它们响应的结果也无法缓存。
- 应用程序缓存可以根据响应的状态代码,认为它是可缓存的。以下状态代码是可缓存的:200、203、204、206、300、301、404、405、410、414 和 501。
- 如果响应中有特定的标头,如 Cache-Control,可防止缓存。
HTTP 标头(header)
- HTTP 标头是用于 HTTP 请求或响应的字段,它传递关于请求或者响应的额外上下文和元数据。
- 例如,请求消息可以使用标头表明它首选的媒体格式,而响应可以使用标头表明返回主体的媒体格式。
- 标头是不区分大小写,开始于行首,后面紧跟着一个 ':' 和与之相关的值。字段值在一个换行符(CRLF)前或者整个消息的末尾结束。
- 根据不同的消息上下文,标头可以分为:
- 请求标头:包含要获取的资源或者客户端自身的更多信息。例如,Accept-* 标头指示响应的允许格式和首选格式。其他标头可用于提供身份验证凭据(Authorization、Token 授权等)、控制缓存或获取有关用户代理(user agent)或引荐来源网址(referrer)等的信息。
- 响应标头:包含有关响应的额外信息,例如响应的位置(Location)、响应时间(Date)、最后更新时间(Last-Modified)或者关于服务器自身的信息(Server,包括名字、版本等)。
- 表示标头:包含消息主体中资源的元数据(例如,编码、MIME 媒体类型、压缩方案等)。包括 Content-Type、Content-Encoding、Content-Language 和 Content-Location。
- 有效负荷标头:包含有关有效载荷数据表示的单独信息,包括内容长度和用于传输的编码。包括 Content-Length、Content-Range、Trailer 和 Transfer-Encoding。
- 并非所有可以出现在请求中的标头都被规范称为请求标头,并非所有出现在响应中的标头都根据规范将其归类为响应标头。例如,Content-Type 就是一个表示标头,在请求中 (如 POST 或 PUT),客户端告诉服务器实际发送的数据类型;在响应中,Content-Type 标头告诉客户端实际返回的内容的内容类型。
- 下图列出了一些与请求和响应相关常见的标头。
响应状态码
用来表明特定 HTTP 请求是否成功完成,归为以下五大类:
- 信息响应 (100–199)
- 成功响应 (200–299)
- 重定向消息 (300–399)
- 客户端错误响应 (400–499)
- 服务端错误响应 (500–599)
Cookie
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据。HTTP 是无状态协议,这意味着服务器不会在两个请求之间保留任何数据(状态),而 Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。浏览器会存储 Cookie 并在下次向同一服务器再发起请求时携带并发送到服务器上。
工作机制如下:
服务器使用Set-Cookie 响应标头向用户代理(一般是浏览器)发送 Cookie 信息。例如:
xml
复制代码
Set-Cookie: <cookie-name>=<cookie-value>
这指示服务器发送标头告知客户端存储一对 Cookie:
ini
复制代码
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[页面内容]
现在,对该服务器发起的每一次新请求,浏览器都会将之前保存的 Cookie 信息通过Cookie 请求标头再发送给服务器。
ini
复制代码
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
Secure 属性和 HttpOnly 属性可以确保 Cookie 被安全发送,并且不会被意外的参与者或脚本访问。
- 标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端。它永远不会使用不安全的 HTTP 发送(本地主机除外),这意味着中间人攻击者无法轻松访问它。不安全的站点(http)无法使用 Secure 属性设置 Cookie。但是,Secure 不会阻止对 Cookie 中敏感信息的访问。例如,有权访问客户端硬盘(如果未设置 HttpOnly 属性,则有权访问 JavaScript)的人可以读取和修改它。
- JavaScript Document.cookie 无法访问带有 HttpOnly 属性的 Cookie,此类 Cookie 仅作用于服务器。例如,持久化服务器端会话的 Cookie 不需要对 JavaScript 可用,而应具有 HttpOnly 属性。此预防措施有助于缓解跨站点脚本(XSS)攻击。
Cookie 主要用于以下三个方面:
- 会话状态管理 - 如用户登录状态、购物车、游戏分数或其它需要记录的信息
- 个性化设置 - 如用户自定义设置、主题和其他设置
- 浏览器行为跟踪 - 如跟踪分析用户行为等
Cookie 曾一度用于客户端数据的存储,因当时并没有其它合适的存储办法而作为唯一的存储手段,但现在推荐使用现代存储 API。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(localStorage 和 sessionStorage)或 IndexedDB。
Cookie vs Session vs Token
由于服务器指定 Cookie 后,浏览器的每次请求都会携带 Cookie 数据,会带来额外的性能开销(尤其是在移动环境下)。
以购物车为例,需要有一个机制记录每个连接的关系,这样我们就知道加入购物车的商品到底属于谁了,每次浏览器请求后 server 都会将本次商品 id 存储在 Cookie 中返回给客户端,客户端会将 Cookie 保存在本地,下一次再将上次保存在本地的 Cookie 传给 server,这样每个 Cookie 都保存着用户信息和商品 id。
但是,随着购物车内的商品越来越多,每次请求的 cookie 也越来越大,这对每个请求来说是一个很大的负担,我只是想将一个商品加入购买车,为何要将历史的商品记录也一起返回给 server?而且,购物车信息其实已经保存在 server 中了。
Session
由于用户的购物车信息都会保存在 Server 中,所以在 Cookie 里只要保存能识别用户身份的信息,知道是谁发起了加入购物车操作即可,这样每次请求后只要在 Cookie 里带上用户的身份信息,请求体里也只要带上本次加入购物车的商品 id,大大减少了 cookie 的体积大小,我们把这种能识别哪个请求由哪个用户发起的机制称为 Session(会话机制),生成的能识别用户身份信息的字符串称为 sessionId。
- 首先用户登录,server 会为用户生成一个 session,为其分配唯一的 sessionId,这个 sessionId 是与某个用户绑定的,也就是说根据此 sessionid(假设为 abc) 可以查询到它到底是哪个用户,然后将此 sessionid 通过 cookie 传给浏览器。
- 之后浏览器的每次添加购物车请求中只要在 cookie 里带上 sessionId=abc 这一个键值对即可,server 根据 sessionId 找到它对应的用户后,把传过来的商品 id 保存到 server 中对应用户的购物车即可。
可以看到通过这种方式再也不需要在 cookie 里传所有的购物车的商品 id 了,大大减轻了请求的负担!另外,cookie 是存储在 client 的,而 session 保存在 server,sessionId 需要借助 cookie 的传递才有意义。
但是,上述情况能正常工作是因为我们假设 server 是单机工作的,实际生产中,为了保障高可用,一般服务器至少需要两台机器,客户端请求后,由负载均衡器(如 Nginx)来决定到底打到哪台机器。