一、Http 协议无状态的含义
1.1有状态协议
常见的许多七层协议实际上是有状态的,例如SMTP协议,它的第一条消息必须是HELO,用来握手,在HELO发送之前其他任何命令都是不能发送的;接下来一般要进行AUTH阶段,用来验证用户名和密码;接下来可以发送邮件数据;最后,通过QUT命令退出。可以看到,在整个传输层上,通信的双方是必须要时刻记住当前连接的状态的,因为不同的状态下能接受的命令是不同的;另外,之前的命令传输的某些数据也必须要记住,可能会对后面的命令产生影响。这种就叫做有状态的协议。
1.2 http为什么说http协议是无状态协议
相反,为什么说HTTP是无状态的协议呢?因为它的每个请求都是完全独立的,每个请求包含了处理这个请求所需的完整的数据,发送请求不涉及到状态变更即使在HTTP/1.1上,同一个连接允许传输多个HTTP请求的情况下,如果第一个请求出错了,后面的请求一般也能够继续处理(当然,如果导致协议解析失败、消息分片错误之类的自然是要除外的)。可以看出,这种协议的结构是要比有状态的协议更简单的,一般来说实现起来也更简单,不需要使用状态机,一个循环就行了。
1.3为什么不改进http协议使之有状态
最初的http协议只是用来浏览静态文件的,无状态协议已经足够,这样实现的负担也很轻(相对来说,实现有状态的代价是很高的,要维护状态,根据状态来操作。)。随着web的发展,它需要变得有状态,但是不是就要修改http协议使之有状态呢?是不需要的。因为我们经常长时间逗留在某一个网页,然后才进入到另一个网页,如果在这两个页面之间维持状态,代价是很高的。其次,历史让http无状态,但是现在对http提出了新的要求,按照软件领域的通常做法是,保留历史经验,在http协议上再加上一层实现我们的目的(“再加上一层,你可以做任何事”)。所以引入了其他机制来实现这种有状态的连接。
1.4无状态协议的优缺点
和许多人想象的不同,会话(Session)支持其实并不是一个缺点,反而是无状态协议的优点,因为对于有状态协议来说,如果将会话状态与连接绑定在一起,那么如果连接意外断开,整个会话就会丢失,重新连接之后一般需要从头开始(当然这也可以通过吸收无状态协议的某些特点进行改进);而HTTP这样的无状态协议,使用元数据(如Cookies头)来维护会话,使得会话与连接本身独立起来,这样即使连接断开了,会话状态也不会受到严重伤害,保持会话也不需要保持连接本身。另外,无状态的优点还在于对中间件友好,中间件无需完全理解通信双方的交互过程,只需要能正确分片消息即可,而且中间件可以很方便地将消息在不同的连接上传输而不影响正确性,这就方便了负载均衡等组件的设计。
无状态协议的主要缺点在于,单个请求需要的所有信息都必须要包含在请求中一次发送到服务端,这导致单个消息的结构需要比较复杂,必须能够支持大量元数据,因此HTTP消息的解析要比其他许多协议都要复杂得多。同时,这也导致了相同的数据在多个请求上往往需要反复传输,例如同一个连接上的每个请求都需要传输Host、 Authentication、 Cookies、 Server等往往是完全重复的元数据,一定程度上降低了协议的效率。
1.5 HTTP协议是无状态协议,这句话本身到底对不对?
实际上,并不全对。HTTP/1.1中有一个 HTTP/1.1 Expect: 100-Continue的功能,它是这么工作的:
- 1.在发送大量数据的时候,考虑到服务端有可能直接拒收数据,客户端发出请求头并附带 Expect:100-continue HTTP的HTTP头,不发送请求体,先等待服务器响应
- 2.服务器收到 Expect:100-Continue 的请求,如果允许上传,发送100 continue 的HTTP响应(同一个请求可以有任意个1xx的响应,均不是最后的 Response,只起到提示性作用);如果不允许,例如不允许上传数据,或者数据大小超出限制,直接返回4xx/5xx的错误
- 3.客户端收到100 Continue的响应之后,继续上传数据
可以看出,这实际上很明显是一个有状态协议的套路,它需要先进行一次握手,然后再真正发送数据。不过,HTTP协议也规定,如果服务端不进行100 Continue的响应,建议客户端在等待较短的时间之后仍然上传数据,以达成与不支持 Expect:100- Continue功能的服务器的兼容,这样可以算是“能有状态则有状态,否则回到无状态的路上”,这样说HTTP1.x 是无状态的协议也是没错的。
至于HTTP/2,它应该算是一个有状态的协议了(有握手和 GOAWAY消息,有类似于TCP的流控),所以以后说“HTTP是无状态的协议”就不太对了,最好说HTTP1.x 是无状态的协议
二、如何解决无状态问题
HTTP协议是无状态的,无状态意味着,服务器无法给不同的客户端响应不同的信息。这样一些交互业务就无法支撑了。 Cookie应运而生。
2.1 Cookie
cookie的传递会经过下边这4步:
- Client 发送 HTTP 请求给 Server
- Server响应,并附带Set-Cookie的头部信息
- Client保存 Cookie, 之后请求 Server 会附带Cookie的头部信息
- Server从 Cookie 知道 Client是谁了,返回相应的响应
Cookie的英文翻译是甜品,使用 Cookie可以自动填写用户名、记住密码等,是给用户的一点甜头。
Server拿到 Cookie后,通过什么信息才能判断是哪个Client呢? 服务器的 SessionID
2.2 Session
如果把用户名、密码等重要隐私都存到客户端的Cookie中,还是有泄密风险。为了更安全,把机密信息保存到服务器上,这就是 Session Session是服务器上维护的客户档案,可以理解为服务器端数据库中有一张user表,里面存放了客户端的用户信息。SessionID就是这张表的主键ID Session信息存到服务器,必然占用内存。用户多了以后,开销必然增大。为了提高效率,需要做分布式,做负载均衡。因为认证的信息保存在内存中,用户访问哪台服务器,下次还得访问相同这台服务器才能拿到授权信息,这就限制了负载均衡的能力。而且 SeesionID存在 Cookie,还是有暴露的风险,比如SCRF(Cross-Site Request Forgery,跨站请求伪造)
如何解决这些问题呢? 基于 Token令牌鉴权。
2.3 Token
首先, Token不需要再存储用户信息,节约了内存。其次,由于不存储信息,客户端访问不同的服务器也能进行鉴权,增强了扩展能力。然后, Token可以采用不同的加密方式进行签名,提高了安全性。 Token就是一段字符串, Token传递的过程跟 Cookie类似,只是传递对象变成了 Token。用户使用用户名、密码请求服务器后,服务器就生成 Token,在响应中返给客户端,客户端再次请求时附带上 Token, 服务器就用这个 Token进行认证鉴权。
Token虽然很好的解决了 Session的问题,但仍然不够完美。服务器在认证 Token的时候,仍然需要去数据库查询认证信息做校验。为了不查库,直接认证,JWT出现了。
2.4JWT
JWT的英文全称是 JSON Web Token。JWT把所有信息都存在自己身上了,包括用户名密码、加密信息等,且以JSON对象存储的。 JWT长相是xxxxx.yyyyy.zzzzz,极具艺术感。包括三部分内容
- Header包括 token类型和加密算法(HMAC SHA256 RSA)
{ "alg": "HS256", "typ": "JWT" }
{"sub":1234567890","name":"John Doe, "admin": true}
- Signature
签名, 把 header 和 payload 用 base64 编码后 “.” 拼接, 加盐 secret(服务器私钥)
HMACSHA256(base64UrLEncode(header)+"." + base64UrlEncode(payload),secret);
最终的 token就是这样一个字符串
eyJhbGci0iJIUzI11Ni9
.eyJzdWIi0iIxMjMONTY30DkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.yK0B4jkGWu7twu8Ts9zju01E10_CPedLJkoJFCan5J4;
给 Token穿个外套
Authorization: Bearer;
这就是我们在请求 Header里面看到的内容格式了。
|