【每日一题】说一下什么是http协议无状态以及怎么解决?

前端印记

共 7456字,需浏览 15分钟

 ·

2021-09-26 17:20

人生苦短,总需要一点仪式感。比如学前端~

说一下什么是http协议无状态以及怎么解决?


  • http 协议无状态

    • 有状态协议

    • 为什么说 http 协议是无状态协议呢?

    • 为什么不改进 http 协议让其有状态

    • 无状态的优缺点

    • http 协议是无状态协议,这句话本身到底对不对?

  • 如果解决无状态的问题?

    • Cookie

    • Session

    • Token

    • JWT


http 协议无状态

有状态协议

常见的许多七层协议实际上是有状态的,例如 SMTP 协议:

  • 它的第一条信息必须是 HELO,用来握手,在 HELO 发送之前其他任何命令都是不能发送的;
  • 接下来一般要进行 AUTH 阶段,用来验证用户名和密码;
  • 接下来就可以发送邮件数据;
  • 最后,通过 QUIT 命令退出。

可以看到,在整个传输层上,通信的双方是必须要时刻记住当前连接的状态的,因为不同的状态下能接受的命令是不同的;另外,之前的命令传输的某些数据也必须要记住,可能会对后面的命令产生影响。这种就叫做有状态的协议

为什么说 http 协议是无状态协议呢?

因为它的每个请求都是完全独立的,每个请求包含了处理这个请求所需的完整的数据,发送请求不涉及到状态变更

即使在 HTTP1.1 上,同一个连接允许传输多个 HTTP 请求的情况下,如果第一个请求出错了,后面的请求一般也能够继续处理(如果导致协议解析失败、消息分片错误之类的自然是要除外的)。可以看出,这种协议的结构是要比有状态的协议更简单的,一般来说实现起来也更简单,不需要使用状态机,一个循环就够了。

为什么不改进 http 协议让其有状态

最初的 http 协议只是用来浏览静态文件,无状态协议已经足够,这样实现的负担也很轻(相对来说,实现有状态的代价是很高的,需要维护状态且根据状态来进行操作)。

随着 web 的发展,请求需要变得有状态,那么的话是不是需要修改 http 协议使之有状态呢?
答案是不需要的。因为我们经常长时间逗留在某一个网页,然后才进入另一个网页。如果在这两个页面之间维持状态,代价是很高的。其次,历史让 http 无状态,但是现在对 http 提出了新的要求,按照软件领略的通常做法是:保留历史记录,在 http 协议上在加上一层实现我们的目的,所以引入了其他机制来实现这种有状态的连接。

总结:代价和历史。

无状态的优缺点

优点

和许多人想象的不同,会话(Session) 支持其实并不是一个缺点,反而是无状态协议的优点。因为对于有状态协议来说,如果将会话状态与连接绑定在一起,那么如果连接意外断开,整个会话就会丢失,重新连接之后一般需要从头开始(当然这也可以通过吸收无状态协议的某些特点进行改进);

而 HTTP 这样的无状态协议,使用元数据(如 Cookies 头)来维护会话,使得会话与连接本身独立起来,这样即使连接断开了,会话状态也不会受到严重伤害,保持会话也不需要保持连接本身。

另外,无状态的优点还在于对中间件友好。中间件不需要完全理解通信双方的交互过程,只需要能正确分片消息即可;而且中间件可以很方便地将消息在不同的连接上传输而不影响正确性,这就方便了负载均衡等组件的设计。

缺点

无状态协议的主要缺点在于:单个请求需要的所有信息都必须要包含在请求中一次发送到服务端,这导致单个消息的结构需要比较复杂,必须能够支持大量元数据,因此 HTTP 消息的解析要比其他许多协议都要复杂得多。同时,这也导致了相同的数据在多个请求上往往需要反复传输,例如同一个连接上的每个请求都需要传输 HostAuthenticationCookiesServer 等往往是完全重复的元数据,在一定程度上降低了协议的效率

一句话来说:请求头太大,还要反复传输。

http 协议是无状态协议,这句话本身到底对不对?

实际上,并不全对。

HTTP/1.1 中有一个 Expect: 100-Continue 的功能,它是这么工作的:

  1. 在发送大量数据的时候,考虑到服务端有可能直接拒收数据,客户端发出请求头并附带 Expect:100-Continue 的 HTTP 头,不发送请求体,先等待服务器响应

  2. 服务器收到 Expect: 100-Continue 的请求,如果允许上传,发送 100 Continue 的 HTTP 响应(同一个请求可以有任意个 lxx 的响应,均不是最后的 Response,只起到提示性作用) ;如果不允许,例如不允许上传数据,或者数据大小超出限制,直接返回 4xx/5xx 的错误

  3. 客户端收到 100 Continue 的响应之后,继续上传数据

可以看出,这实际上很明显是一个有状态协议的套路,它需要先进行一次握手,然后再真正发送数据。

不过,HTTP 协议也规定,如果服务端不进行 100 Continue 的响应,建议客户端在等待较短的时间之后仍然上传数据,以达成与不支持 Expect: 100-Continue 功能的服务器的兼容,这样可以算是“能有状态则有状态,否则回到无状态的路上”,这样说 HTTP1.x 是无状态的协议也是没错的。

至于 HTTP/2,它应该算是一个有状态的协议了(有握手和 GOAWAY 消息,有类似于 TCP 的流控),所以以后说“HTTP 是无状态的协议”就不太对了,最好说 HTTP 1.x 是无状态的协议”


如果解决无状态的问题?

HTTP 协议是无状态的,无状态意味着:服务器无法给不同的客户端响应不同的信息。这样一些交互业务就无法支撑了,Cookie 应运而生。

Cookie

cookie 的传递会经过:

  1. 客户端 发送 HTTP 请求给 服务端
  2. 服务端 响应,并附带 Set-Cookie 的头部信息
  3. 客户端 保存 Cookie。后续请求 客户端 会附带 Cookie 的头部信息
  4. 服务端 从 Cookie 知道 客户端 的身份,验证没有问题后返回相应的响应

问题:服务端 拿到 Cookie 后,通过什么信息才能判断是哪个 客户端 呢?

  • 服务器的 SessionID。

Session

如果把用户名、密码等重要隐私都存到客户端的 Cookie 中,还是有泄密风险。

为了更安全,把机密信息保存到服务器上,这就是 Session

Session 是服务器上维护的客户档案,可以理解为服务器端数据库中有一张 user 表,里面存放了客户端的用户信息。SessionID 就是这张表的主键 ID。

Session的弊端:

  • Session 信息存到服务器,必然占用内存。用户多了以后,开销必然增大。为了提高效率,需要做分布式,做负载均衡。
  • 因为认证的信息保存在内存中,用户访问哪台服务器,下次还得访问相同这台服务器才能拿到授权信息,这就限制了负载均衡的能力
  • 而且 SeesionID 存在 Cookie,还是有暴露的安全风险,比如 CSRF(Cross-SiteRequest Forgery,跨站请求伪造)。

问题:如何解决这些问题呢?

  • 基于 Token 令牌鉴权。

Token

Token的优点

  • 首先,Token 不需要再存储用户信息,节约了内存
  • 其次,由于不存储信息,客户端访问不同的服务器也能进行鉴权,增强了扩展能力
  • 然后,Token 可以采用不同 的加密方式进行签名,提高了安全性

Token 就是一段字符串。
Token 传递的过程跟 Cookie 类似,只是传递对象变成了 Token:

  • 用户使用用户名、密码请求服务器后,
  • 服务器生成 Token并在响应中返给客户端。
  • 客户端再次请求时附带上 Token,服务器取到 Token 进行认证鉴权。
  • 认证通过,服务器返回数据。认证不通过,跳转登陆。

问题:Token 虽然很好的解决了 Session 的问题,但仍然不够完美。服务器在认证 Token 的时候,仍然需要去数据库查询认证信息做校验。

  • 为了不查库直接认证,JWT 出现了。

JWT

JWT 的英文全称是 JSON Web Token

JWT 把所有信息都存在自己身上了,包括用户名、密码、加密信息等,且以 JSON 对象存储的。

JWT 包括三部分内容:

  1. Header 包括 token 类型和加密算法(HMACSHA256 RSA)
{  
  "alg""HS256",
  "type""JWT"
}
  1. Payload 传入内容
{  
  "sub""1234567890",  
  "name""John Doe",  
  "admin"true
}
  1. Singnature:签名,把 header 和 payload 用 base64 编码后,用"."拼接,校验 secret(服务器私钥)
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);

最终的 token 就是这样的格式:

Bearer = eyJhbGciOiJIUzI1NiJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.yKOB4jkGWu7twu8Ts9zju01E10_CPedLJkoJFCan5J4;

将Token放在请求头里自动传递:

Authorization: Bearer;

总结:

  1. HTTP无状态,为了每次请求标识身份,采用Cookie在请求的时候携带。后段获取Cookie后,Cookie传递的就是供服务器查询身份用的SessionID。而将SessionID对应的user信息存在服务器的内存里。
  2. 但是又因为Cookie不够安全、容易被攻击;存在服务器内存中开销大占用资源但还不方便分布式部署,于是出现了Token:Token可以理解为加密后的Cookie,只不过加密算法不同且只有后端逻辑知道,所以比Cookie更安全。
  3. 但是Token 和Cookie一样,认证信息存在数据库中还需要服务端去获取、对比、验证是否一致。因此出现JWT,将用户名、密码、加密信息等都打包成一个字符串给客户端,客户端下次返回后再解密。优点类似HTTPS的加密过程。

所有《每日一题》的 知识大纲索引脑图 整理在此:https://www.yuque.com/dfe_evernote/interview/everyday
你也可以点击文末的 “阅读原文” 快速跳转


END
愿你历尽千帆,归来仍是少年。

让我们一起携手同走前端路!

关注公众号回复【加群】即可

浏览 17
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报