HTTP 协议

HTTP 协议

《图解 HTTP》阅读笔记

3.1 HTTP 常见面试题 | 小林coding (xiaolincoding.com)

HTTP 基础知识

HTTP 协议用于客户端和服务端之间的双向通信,使用 URI 定位互联网上的资源,通过请求报文响应报文建立通信

HTTP 协议基于 TCP 协议,默认应用端口为80

Attention

HTTP/3改用UDP协议

HTTP报文

报文内容

请求报文是由请求方法请求 URI协议版本可选的请求首部字段内容实体构成的,编码格式为ASCII

|600

响应报文基本上由协议版本状态码(表示请求成功或失败的数字代码)、用以解释状态码的原因短语可选的响应首部字段以及实体主体构成

|600

请求报文

响应报文

报文(message):HTTP 通信中的基本单位,由8位组字节流(octet sequence,其中 octet 为 8 个比特)组成,通过 HTTP 通信传输。
实体(entity):作为请求或响应的有效载荷数据(补充项)被传输,其内容由实体首部实体主体组成。

通常,报文主体等于实体主体,传输中进行编码操作时实体主体的内容发生变化

首部字段

HTTP 首部字段格式为 {首部字段名}: {字段值}
首部字段用于给浏览器和服务器提供报文主体大小、所使用的语言、认证信息等内容。
对单一报文中相同首部字段名重复出现的处理在规范内尚未明确,依赖于浏览器的内部处理逻辑
标准中没有对每个协议头字段的名称和值的大小设置任何限制,也没有限制字段的个数。然而,出于实际场景及安全性的考虑,大部分的服务器、客户端和代理软件都会实施一些限制

HTTP 首部字段根据是否缓存代理分成 2 种类型:

根据实际用途又分为以下4种类型:

常见首部字段

具体的首部字段内容及说明可查阅 HTTP头字段-维基百科

请求报文首部示例:

GET / HTTP/1.1
Host: hackr.jp
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*; q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

响应报文首部示例:

HTTP/1.1 200 OK
Date: Fri,13 Jul 2012 02:45:26 GMT
Server: Apache
Last-Modified: Fri,31 Aug 2007 02:02:20 GMT
BTag: "45bae1-16a-46d776ac"
Accept-Ranges: bytes
Content-Length: 362
Connection: close
Content-Type: text/html
Content-Encoding: gzip

HTTP请求方法

常见HTTP方法包括:

方法 作用 协议
GET 从服务器获取指定资源 1.0、1.1
POST 在服务器新建资源 1.0、1.1
PUT 在服务器更新资源(请求方提供完整资源) 1.0、1.1
DELETE 从服务器删除资源 1.0、1.1
PATCH 在服务器更新资源(请求方提供改变的属性) 1.1
HEAD 获取报文首部字段 1.0、1.1
OPTIONS 询问支持方法 1.1
TRACE 追踪路径 1.1
CONNECT 要求用隧道协议连接代理 1.1

GET 方法是安全且幂等的,无论操作多少次,服务器上的数据都安全且每次的结果相同
所以浏览器可以对 GET 请求的数据做缓存,而且在浏览器中 GET 请求可以保存为书签
POST 是新增或提交数据的操作,会修改服务器上的资源,所以是不安全且非幂等的
所以浏览器一般不会缓存 POST 请求,也不能把 POST 请求保存为书签

幂等

假如在不考虑诸如错误或者过期等问题的情况下,若干次请求的副作用与单次请求相同或者根本没有副作用,那么这些请求方法就能够被视作“幂等”的

GET,HEAD,PUT 和 DELETE 方法都有幂等属性,同样由于根据协议,OPTIONS,TRACE 都不应有副作用,因此也理所当然也是幂等的

Notes

 GET 请求可以携带 body ,RFC 规范定义的 GET 请求是获取资源,所以根据这个语义不需要用到 body

HTTP 的 GET 请求不一定具有幂等性,在实际应用中,GET 请求通常用于获取资源的操作,例如读取文章、获取用户信息等。如果 GET 请求只是获取资源,不对资源状态做出修改,则可以确保具有幂等性。也就是说,多次发起相同的 GET 请求,得到的响应结果应该是相同的
但是,如果 GET 请求携带了一些参数,比如在 URL 中添加了时间戳或随机数等不同的标识符,那么每次请求得到的结果可能不同,因此 GET 请求就不具有幂等性

此外,一些 web 应用程序可能会在 GET 请求中使用类似于购买商品或者转帐等修改资源状态的操作,这样的 GET 请求就不具有幂等性,因为相同的请求可能会对资源状态做出不同的修改

总之,GET 请求是否具有幂等性取决于具体的场景和实现。在编写 web 应用程序时,需要根据具体的需求和场景来确定请求的幂等性。

数据传输

需传输的数据编码压缩后,分块传输,由客户端进行解码恢复

对于包含多种数据类型的实体,采用多部分对象集合并在首部字段中加入Content-type

对于断连后的重新传输,可以通过范围请求指定下载的实体范围,仅请求部分资源,请求报文首部字段包含Range

HTTP 缓存

HTTP 缓存-小林 coding

可将一些具有重复性的 HTTP 请求及其响应缓存在本地以提升性能

强制缓存

强制缓存指只要浏览器判断缓存没有过期,则直接使用本地缓存
强制缓存利用 HTTP 首部字段中的 Cache-Control (相对时间)和 Expires (绝对时间)字段实现,它们都用来表示资源在客户端缓存的有效期:
如果 HTTP 响应头部同时有 Cache-ControlExpires 字段的话,Cache-Control 的优先级高于 Expires

Cache-control 选项更多一些,设置更加精细,所以建议使用 Cache-Control 来实现强制缓存
具体的实现流程如下:

协商缓存

协商缓存指客户端与服务端协商之后,通过协商结果来判断是否使用本地缓存
协商缓存的响应报文状态码为304,有两种实现方式

  1. 通过请求头部中的 If-Modified-Since 字段与响应头部中的 Last-Modified 字段实现
    • Last-Modified:标示这个响应资源的最后修改时间
    • If-Modified-Since:当资源过期时,浏览器发现响应头中有 Last-Modified 声明,则再次发起请求的时候使用 If-Modified-Since 告知服务器 Last-Modified 值,服务器将其与被请求资源的最后修改时间进行对比
      • 如果最后修改时间较新(大),说明资源又被改过,则返回最新资源,HTTP 200 OK
      • 如果最后修改时间较旧(小),说明资源无新修改,响应 HTTP 304 走缓存
  2. 通过请求头部中的 If-None-Match 字段与响应头部中的 ETag 字段实现
    • Etag:唯一标识响应资源
    • If-None-Match:当资源过期时,浏览器发现响应头里有 Etag,则再次发起请求时使用 If-None-Match 告知服务器 Etag 值,服务器收到请求后进行比对,如果资源没有变化返回 304,如果资源变化了返回 200

相较于 Last-ModifiedETag 有如下优势

  1. 可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题
  2. 可能有些文件是在秒级以内修改的,If-Modified-Since 能检查到的粒度是秒级的,使用 Etag就能够保证这种需求下客户端在 1 秒内能刷新多次
  3. 有些服务器不能精确获取文件的最后修改时间

协商缓存需要配合强制缓存中 Cache-Control 字段使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求

http缓存.png (1348×1122) (xiaolincoding.com)|600

状态管理

图解|cookie、session、token的那些事儿 - 掘金 (juejin.cn)

HTTP 协议为无状态协议,不对之前发生过的请求和响应的状态进行管理,需使用Cookies记录状态

Cookies

客户端和服务端首次通信时,服务器端通过响应报文内的 Set-Cookie 的首部字段信息(格式为 key=value)通知客户端保存服务器发送的 Cookie
下次客户端向该服务器发送请求时自动在请求报文中加入 Cookie 值后发送,服务端检查 Cookie 获取状态信息

服务器有时会在响应头里添加多个 Set-Cookie,但客户端发送时不需要用多个 Cookie 字段,只要在一行里用 ; 隔开就行

|600

由于 Cookie 通常记录客户的关键识别信息,需要使用属性来保护,防止外泄或窃取

Cookie 应用:

Session

如果说 Cookie 是客户端行为,那么 Session 就是服务端行为
Cookie 机制在最初和服务端完成交互后,保持状态所需的信息都将存储在客户端,后续直接读取发送给服务端进行交互
Session 代表服务器与浏览器的一次会话过程,将用户的所有活动信息、上下文信息、登录信息等都存储在服务端,只是生成一个唯一标识 ID 发送给客户端,后续的交互将没有重复的用户信息传输,取而代之的是唯一标识 ID

  1. 当客户端第一次请求 session 对象时候,服务器会为客户端创建一个 session,并将通过特殊算法算出一个 session 的 ID,用来标识该 session 对象
  2. 当浏览器下次请求别的资源的时候,浏览器会将 sessionID 放置到请求头中,服务器接收到请求后解析得到 sessionID,服务器找到该 id 的 session 来确定请求方的身份和一些上下文信息

session 的实现方式包括 Cookieurl 重写
cookie 为首选方式,url 重写则在禁止 cookie 的情况下提供服务,将会话标识号以参数形式附加在超链接的 URL 地址后面的技术称为 URL 重写

问题:

Token - JWT

Token 是由服务端生成并发放给客户端,具有时效性的一种验证身份的手段
原理是服务器认证以后生成一个 JSON 对象发回给用户,之后用户与服务端通信的时候,都发回这个 JSON 对象,服务器完全依赖该对象认定用户身份

|625

  1. 客户端将用户的账号和密码提交给服务器
  2. 服务器对其进行校验,通过则生成一个 token 值,将其保存在数据库,同时也返回给客户端,作为后续的请求交互身份令牌
  3. 客户端拿到服务端返回的 token 值后,可将其保存在本地,以后每次请求服务器时都携带该 token,提交给服务器进行身份校验
  4. 服务器接收到请求后,解析出其中的 token,再根据相同的加密算法和参数生成 token 与客户端的 token 进行对比,一致则通过,否则拒绝服务
  5. token 验证通过,服务端就可以根据该 token 中的 uid 获取对应的用户信息,进行业务请求的响应

优势

劣势

Token 设计

以 JSON Web Token(JWT)为例,Token 主要由 3 部分组成:

|625

服务器生成的 JWT token 的形式为 Header.Payload.Signature,其中 header 和 payload 的信息不做加密,只做一般的 base64URL 编码,signature 使用 header 里指定的签名算法外加服务器密钥加密生成

之后服务端每次收到 token 后剥离出 header 和 payload 获取算法、用户、过期时间等信息,然后根据自己的密钥生成 signature,并与 token的 signature 进行一致性验证,实现用 CPU 加解密的时间换取存储空间

JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分
JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限,也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑
JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限,为了减少盗用,JWT 的有效期应该设置得比较短,对于一些比较重要的权限,使用时应该再次对用户进行认证
为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输

返回内容

服务器存在多个相同内容的页面时,通过内容协商机制返回合适内容
内容协商机制指客户端和服务器端就响应的资源内容进行交涉,然后提供给客户端最为适合的资源。
内容协商以响应资源的语言、字符集、编码方式等作为判断的基准。

机制类型:

跨域

前端 - 什么是跨域? 出现原因及解决方法 - 个人文章 - SegmentFault 思否

跨域指的是不同源之间的资源访问,当一个请求 url 的协议、域名、端口三者之间的任意一个与当前页面 url 不同即为跨域
只要请求的 url 有以下不同,都属于“跨域”:

产生原因

在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问问题

浏览器的同源策略限制会阻止一个域的 js 脚本和另外一个域的内容进行交互,如果缺少同源策略,浏览器很容易受到 XSS、CSFR 等攻击
所谓同源(即在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)

非同源会出现的限制

解决方法

nginx 反向代理
正向代理:a-->b 访问不了,可以使用中间服务器 c, 先访问 c 再从 c 到 b,访问的目的地明确,但是用户不知道中间的代理服务器 (忽略代理服务器)
反向代理:a--> c <--b,a 明确访问 c 代理服务器,但是不知道 c 的内容从哪里来,c 反向从别的地方拿来数据 (忽略目标地址)

CORS 解决跨域(添加响应头)
浏览器先询问 b,b 允许 a 访问

CORS 机制通过允许服务器标示除了它自己以外的其他域,使得浏览器允许这些域访问加载自己的资源
浏览器将 CORS 请求分为简单请求和非简单请求
简单请求会在发送时自动在 HTTP 请求头加上 Origin 字段,来标明当前是哪个源(协议+域名+端口),服务端来决定是否放行
非简单请求则会先发一个 OPTIONS 预检请求给服务端,当通过了再发正常的 CORS 请求

CORS 标准新增了一组 HTTP 标头字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源
规范要求对那些可能对服务器数据产生副作用的 HTTP 请求方法,浏览器必须首先使用 OPTIONS 方法发起一个预检请求,从而获知服务端是否允许该跨源请求,服务器确认允许之后,才发起实际的 HTTP 请求
在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(例如 Cookie 和 HTTP 认证相关数据)

通过 jsonp 解决跨域(老方法)
通常为了减轻web服务器的负载把js、css、图片等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许。

html 中有的标签天然支持跨域,比如 <script src="http://www.baidu.com"></script>,但是只支持 get 请求

1a3-HTTP发展

用户身份认证

常见认证信息:密码、动态令牌、数字证书、生物认证、IC卡等

HTTP/1.1使用的认证方式:BASIC 认证(基本认证)、DIGEST 认证(摘要认证)、SSL 客户端认证、FormBase 认证(基于表单认证)等

Basic 认证

DIGEST认证

  1. ①中首部字段 WWW-Authenticate 内必须包含 realmnonce 这两个字段的信息。客户端就是依靠向服务器回送这两个值进行认证的。
  2. ②中首部字段 Authorization 内必须包含 usernamerealmnonceuriresponse 的字段信息。
    • realmnonce 就是之前从服务器接收到的响应中的字段。
    • usernamerealm 限定范围内可进行认证的用户名
    • uri(digest-uri)即 Request-URI 的值,经代理转发后Request-URI的值可能被修改,因此事先会复制一份副本保存在uri内。
    • response 也可叫做 Request-Digest,存放经过 MD5 运算后的密码字符串,形成响应码。

SSL客户端认证

借由 HTTPS 的客户端证书完成认证

步骤:

  1. 接收到需要认证资源的请求,服务器会发送 CertificateRequest 报文,要求客户端提供客户端证书。
  2. 用户选择将发送的客户端证书后,客户端会把客户端证书信息以 Client Certificate 报文方式发送给服务器。
  3. 服务器验证客户端证书验证通过后方可领取证书内客户端的公开密钥,然后开始 HTTPS 加密通信。

多数情况下和表单认证组合形成一种双因素认证(Two-factor authentication)来使用

表单认证

基于表单的认证方法不是在 HTTP 协议中定义的,客户端会向服务器上的 Web 应用程序发送登录信息(Credential),按登录信息的验证结果认证。

用户身份认证多半基于表单认证
基于表单认证的标准规范尚未有定论,一般会使用 Cookie 来管理 Session(会话)

  1. 客户端使用HTTPS进行用户输入数据(用户名&密码)的发送
  2. 服务器将用户认证状态和Session ID绑定后向用户发放包含Session ID的cookies
  3. 之后客户端发送请求时服务器验证接收到的Cookies中的Session ID识别用户和其认证状态

服务器端应如何保存用户提交的密码等登录信息等也没有标准化,通常的安全做法是先利用给密码加盐(salt)的方式增加额外信息,再使用散列(hash)函数计算出散列值后保存。

Web 服务器

虚拟主机

HTTP/1.1 规范允许一台 HTTP 服务器搭建多个 Web 站点,因此利用虚拟主机可令一个IP地址配对多个URI
互联网通过DNS服务将URI解析为IP进行连接,连接至服务器上通过首部中的Host指定的URI确认具体的访问域名

转发服务器

代理/缓存

代理服务器(proxy server)也叫Web缓存器(Web cache),其接收客户端发送的请求后转发给其他服务器。代理不改变请求 URI,会直接发送给前方持有资源的目标服务器。
每次通过代理服务器转发请求或响应时,会追加写入Via首部信息

用途:利用缓存技术减少网络带宽的流量,组织内部针对特定网站的访问控制,以获取访问日志为主要目的等等

类型缓存/非缓存代理透明/非透明代理
代理转发响应时,缓存代理(Caching Proxy)会预先将资源的副本(缓存)保存在代理服务器上
转发请求或响应时,不对报文做任何加工的代理类型被称为透明代理

缓存服务器是代理服务器的一种,并归类在缓存代理类型中,缓存可以存在于缓存服务器内或客户端浏览器中。判定缓存过期后,会向源服务器确认资源的有效性,若判断浏览器缓存失效,浏览器会再次请求新资源。缓存服务器既是服务器又是客户端。 当它接收浏览器的请求并发回响应时是服务器。当它向初始服务器发出请求并接收响应时是客户端。

对于缓存的版本问题,HTTP使用条件GET(conditional GET)方法,在GET方法的首部字段中加入If-Modified-Since字段以确认缓存文件是否改变

网关

转发其他服务器通信数据的服务器

用途:可以由 HTTP 请求转化为其他协议通信

隧道

在相隔甚远的客户端和服务器两者之间进行中转,并保持双方通信连接的应用程序

用途:用 SSL 等加密手段进行通信,确保客户端能与服务器进行安全的通信

CN.0a1a.HTTP相关知识