# http
- 物理层 主要作用定义物理设备如何传输数据
- 数据链路层 在通信的实体间建立数据链路连接
- 网络层 为数据在结点之间的传输创建逻辑链路
- 传输层 为用户提供可靠的端到端服务,传输层向高层屏蔽了下层数据通信的细节
- 应用层
# http历程
HTTP/0.9 HTTP是基于TCP/IP协议的应用层协议。它不涉及数据包传输,主要规定了客户端和服务器端之间的通信格式,默认使用80端口号。只有get命令。
HTTP/1.0 任何格式的内容都可以发送,如文字、图片、视频、二进制文件。有get命令、post命令及head命令 http请求和回应的格式也变了,除了数据部分,每次通信还包括头信息,描述一些元数据。还新增状态码、多字符集支持、多部分发送、权限、缓存、内容编码
请求格式:
GET / HTTP/1.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*
请求报文由请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。
第一部分:请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本
GET说明请求类型为GET,[/562f25980001b1b106000338.jpg]为要访问的资源,该行的最后一部分说明使用的是HTTP1.1版本。
第二部分:请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息
从第二行起为请求头部,HOST将指出请求的目的地.User-Agent,服务器端和客户端脚本都能访问它,它是浏览器类型检测逻辑的重要基础.该信息由你的浏览器来定义,并且在每个请求中自动发送等等
第三部分:空行,请求头部后面的空行是必须的
即使第四部分的请求数据为空,也必须有空行
第四部分:请求数据也叫主体,可以添加任意的其他数据
回应格式:
HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84
HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
第一部分:状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok)
第二部分:消息报头,用来说明客户端要使用的一些附加信息
第二行和第三行为消息报头, Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8
第三部分:空行,消息报头后面的空行是必须的
第四部分:响应正文,服务器返回给客户端的文本信息。
空行后面的html部分为响应正文。
客户端请求的时候,可以使用Accept字段声明自己可以接受哪些数据格式。如Accept: /,表示客户端可以接受任何类型的数据。
HTTP/1.0 版,每个TCP连接只能发送一个请求。发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接。
HTTP/1.1
HTTP/1.1 中引入了
持久连接的概念(persistent connection),通过设置Connection头部为keep-alive的方式,可以让 TCP 连接不会关闭。该功能避免了 TCP 连接的重新建立,客户端可在已建立的 TCP 连接上,长时间地对同一个服务端的发起请求。客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。不过,规范的做法是,客户端在最后一个请求时,发送Connection: close,明确要求服务器关闭TCP连接。
对于同一个域名,大多数浏览器允许同时建立6个持久连接。
引入了
管道机制(pipelining),即在同一个TCP连接里面,客户端可以同时发送多个请求。这样就进一步改进了HTTP协议的效率。管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B请求。
新增put、patch、head、option、delete;客户端请求的头信息新增host字段,用来指定服务器的域名。
虽然1.1版允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着。这称为"队头堵塞"。两种方法:一是减少请求数,二是同时多开持久连接。如合并脚本和样式表、将图片嵌入CSS代码、域名分片等。
HTTP/2
HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧"(frame):头信息帧和数据帧。
二进制协议的一个好处是,可以定义额外的帧。HTTP/2 定义了近十种帧。
HTTP/2 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了"队头堵塞"。如在一个TCP连接里面,服务器同时收到了A请求和B请求,于是先回应A请求,结果发现处理过程非常耗时,于是就发送A请求已经处理好的部分, 接着回应B请求,完成后,再发送A请求剩下的部分。这样双向的、实时的通信,称为多工。
HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。HTTP/2 将每个请求或回应的所有数据包,称为一个数据流(stream)。每个数据流都有一个独一无二的编号。数据包发送的时候,都必须标记数据流ID,用来区分它属于哪个数据流。另外还规定,客户端发出的数据流,ID一律为奇数,服务器发出的,ID为偶数。客户端还可以指定数据流的优先级。
引入了头信息压缩机制(header compression)。一方面,头信息使用gzip或compress压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。
HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。如客户端请求一个网页,这个网页里面包含很多静态资源。正常情况下,客户端必须收到网页后,解析HTML源码,发现有静态资源,再发出静态资源请求。其实,服务器可以预期到客户端请求网页后,很可能会再请求静态资源,所以就主动把这些静态资源随着网页一起发给客户端了。
HTTP/1.1 到 HTTP/2,主要实现了多个请求的复用。 HTTP/2 通过将 HTTP 消息拆分为独立的帧,进行交错发送,实现在同一个连接上并行多个请求,来减少网络请求的延迟。为了实现多路复用,HTTP/2 协议对 HTTP 头部进行了二进制编码,因此不再语义可读。除此之外,HTTP2 还实现了 Header 压缩、服务端主动推动、流优先级等能力。
HTTP/2 到 HTTP/3,主要实现了基于 UDP 协议、更快的传输。 HTTP/3 使用了基于 UDP 的 QUIC 协议,实现了又快又可靠的传输。由于 UDP 协议中没有错误检查内容,因此可以更快地实现通信。同时,QUIC 协议负责合并纠错、重建丢失的数据,解决了 UDP 协议传输丢包的问题。
总的来说,HTTP 协议的演变过程主要围绕着传输效率和速度上的优化,我们可以通过升级 HTTP 协议来优化前端应用。除此之外,我们在日常的工作中,同样可以借鉴 HTTP 协议的优化手段。比如,可以使用资源压缩、资源复用等技术手段,来优化前端性能。技术常常是通用的,我们在学习一些看起来不相关的内容时,会发现其实很多技术转变都是值得思考和参考的。
# HTTP首部
有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。
- 通用首部字段
- 请求首部字段
响应首部字段
实体首部字段
# 短连接与长连接
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 Connection : close;
在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 Connection : Keep-Alive。
那么http如何判断一个报文结束?所有http不外乎2情况:
无entity body,则\r\n\r\n(两个回车符),判断。
有entity body,则用Content-Length“字段值判断。
参考http消息包的结束标记
- 流水线 默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
流水线是在同一条长连接上发出连续的请求,而不用等待响应返回,这样可以避免连接延迟。
COOKIE HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。但是这意味着在同一个 TCP 连接中,先后发起的请求之间没有任何关系。这给服务端带来了挑战:用户在同一个网站中进行连续的操作,服务端无法知道这些操作来自哪里。HTTP/1.1 引入 Cookie 来保存状态信息。
使用 HTTP Cookie 可以解决这个问题。当服务端将 HTTP 响应返回给客户端时,通过在响应头里面添加一个Set-Cookie信息,浏览器收到带Set-Cookie信息的响应后会将 Cookie 保存,在后面发送给该服务端的每个请求中,都会自动带上 Cookie 信息。服务端根据 Cookie 信息,就能取得客户端的数据信息。
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。
由于 Cookie 信息是被浏览器识别并自动保存和发送的,因此在默认情况下,浏览器关闭之后它就会被自动删除。但我们也可以通过指定过期时间(Expires)或者有效期(Max-Age),来让 Cookie 获得更久的有效期。
需要注意的是,某个网站在设置了 Cookie 之后,所有符合条件(有效期、域名、路径、适用站点等)的请求都会被自动带上 Cookie。这带来了一个 Web 安全隐患:服务端只知道请求来自某个用户的浏览器,却不知道请求本身是否用户自愿发出的。
利用这一漏洞,攻击者可通过一些技术手段(图片地址、超链接等)欺骗用户的浏览器访问曾经认证过的网站,并利用用户的登录态进行一些操作,可能导致用户信息泄露、资产被转移、在不知情的情况下发送信息等,带来了恶劣的后果。这便是我们常说的 Web 安全问题之一:跨站请求伪造(CSRF)。
为了应对这种情况,我们可以校验 HTTP 请求头中的Referer字段,这个字段用以标明请求来源于哪个地址。但由于该字段可能会被篡改,因此只能作为辅助校验手段。
防范跨站请求伪造攻击的有效方法,就是避免依赖浏览器自动带上的 Cookie 信息。我们可以使用其他方式校验用户登录态,比如将用户登录态保存在浏览器缓存中,在发送请求的时候添加用于标识用户的参数值,现在大多数应用也是使用 Token 来进行用户标识。
除了 HTTP Cookie 之外,浏览器中 HTTP 缓存机制也同样依赖 HTTP 协议。
Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。
- 用途 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
个性化设置(如用户自定义设置、主题等)
浏览器行为跟踪(如跟踪分析用户行为等)
- 创建过程 服务器发送的响应报文包含 Set-Cookie 首部字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。
HTTP/1.0 200 OKContent-type: text/htmlSet-Cookie: yummy_cookie=chocoSet-Cookie: tasty_cookie=strawberry[page content] 客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。
GET /sample_page.html HTTP/1.1Host: www.example.orgCookie: yummy_cookie=choco; tasty_cookie=strawberry 3. 分类 会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
持久性 Cookie:指定一个特定的过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; 4. 作用域 Domain 标识指定了哪些主机可以接受 Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了 Domain,则一般包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。
Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F (/) 作为路径分隔符,子路径也会被匹配。例如,设置 Path=/docs,则以下地址都会匹配:
/docs
/docs/Web/
/docs/Web/HTTP
- JAVASCRIPT 通过 document.cookie 属性可创建新的 Cookie,也可通过该属性访问非 HttpOnly 标记的 Cookie。
document.cookie = yummy_cookie=choco;document.cookie = tasty_cookie=strawberry;console.log(document.cookie);
- HTTPONLY 标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 document.cookie API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
SECURE 标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。
SESSION 除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。
使用 Session 维护用户登录状态的过程如下:
用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID;
服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中;
客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。
应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。
浏览器禁用 COOKIE 此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外,不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。
COOKIE 与 SESSION 选择 Cookie 只能存储 ASCII 码字符串,而 Session 则可以存取任何类型的数据,因此在考虑数据复杂性时首选 Session;
Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;
对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。
缓存
- 优点 缓解服务器压力;
降低客户端获取资源的延迟:缓存通常位于内存中,读取缓存的速度更快。并且缓存在地理位置上也有可能比源服务器来得近,例如浏览器缓存。
- 实现方法 让代理服务器进行缓存;
让客户端浏览器进行缓存;
- CACHE-CONTROL HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。
3.1 禁止进行缓存
no-store 指令规定不能对请求或响应的任何一部分进行缓存。
Cache-Control: no-store 3.2 强制确认缓存
no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效才将能使用该缓存对客户端的请求进行响应。
Cache-Control: no-cache 3.3 私有缓存和公共缓存
private 指令规定了将资源作为私有缓存,只能被单独用户所使用,一般存储在用户浏览器中。
Cache-Control: private public 指令规定了将资源作为公共缓存,可以被多个用户所使用,一般存储在代理服务器中。
Cache-Control: public 3.4 缓存过期机制
max-age 指令出现在请求报文中,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。
max-age 指令出现在响应报文中,表示缓存资源在缓存服务器中保存的时间。
Cache-Control: max-age=31536000 Expires 首部字段也可以用于告知缓存服务器该资源什么时候会过期。
Expires: Wed, 04 Jul 2012 08:26:05 GMT 在 HTTP/1.1 中,会优先处理 max-age 指令;
在 HTTP/1.0 中,max-age 指令会被忽略掉。
- 缓存验证 需要先了解 ETag 首部字段的含义,它是资源的唯一标识。URL 不能唯一表示资源,例如http://www.google.com/有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。
ETag: 82e22293907ce725faf67773957acd12
可以将缓存资源的 ETag 值放入 If-None-Match 首部,服务器收到该请求后,判断缓存资源的 ETag 值和资源的最新 ETag 值是否一致,如果一致则表示缓存资源有效,返回 304 Not Modified。
If-None-Match: 82e22293907ce725faf67773957acd12
Last-Modified 首部字段也可以用于缓存验证,它包含在源服务器发送的响应报文中,指示源服务器对资源的最后修改时间。但是它是一种弱校验器,因为只能精确到一秒,所以它通常作为 ETag 的备用方案。如果响应首部字段里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 OK。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 Not Modified 响应。
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
内容协商 通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。
- 类型 1.1 服务端驱动型
客户端设置特定的 HTTP 首部字段,例如 Accept、Accept-Charset、Accept-Encoding、Accept-Language,服务器根据这些字段返回特定的资源。
它存在以下问题:
服务器很难知道客户端浏览器的全部信息;
客户端提供的信息相当冗长(HTTP/2 协议的首部压缩机制缓解了这个问题),并且存在隐私风险(HTTP 指纹识别技术);
给定的资源需要返回不同的展现形式,共享缓存的效率会降低,而服务器端的实现会越来越复杂。
1.2 代理驱动型
服务器返回 300 Multiple Choices 或者 406 Not Acceptable,客户端从中选出最合适的那个资源。
- VARY Vary: Accept-Language 在使用内容协商的情况下,只有当缓存服务器中的缓存满足内容协商条件时,才能使用该缓存,否则应该向源服务器请求该资源。
例如,一个客户端发送了一个包含 Accept-Language 首部字段的请求之后,源服务器返回的响应包含 Vary: Accept-Language 内容,缓存服务器对这个响应进行缓存之后,在客户端下一次访问同一个 URL 资源,并且 Accept-Language 与缓存中的对应的值相同时才会返回该缓存。
内容编码 内容编码将实体主体进行压缩,从而减少传输的数据量。
常用的内容编码有:gzip、compress、deflate、identity。
浏览器发送 Accept-Encoding 首部,其中包含有它所支持的压缩算法,以及各自的优先级。服务器则从中选择一种,使用该算法对响应的消息主体进行压缩,并且发送 Content-Encoding 首部来告知浏览器它选择了哪一种算法。由于该内容协商过程是基于编码类型来选择资源的展现形式的,在响应的 Vary 首部至少要包含 Content-Encoding。
范围请求 如果网络出现中断,服务器只发送了一部分数据,范围请求可以使得客户端只请求服务器未发送的那部分数据,从而避免服务器重新发送所有数据。
- Range
在请求报文中添加 Range 首部字段指定请求的范围。
GET /z4d4kWk.jpg HTTP/1.1Host: i.imgur.comRange: bytes=0-1023 请求成功的话服务器返回的响应包含 206 Partial Content 状态码。
HTTP/1.1 206 Partial ContentContent-Range: bytes 0-1023/146515Content-Length: 1024...(binary content) 2. Accept-Ranges
响应首部字段 Accept-Ranges 用于告知客户端是否能处理范围请求,可以处理使用 bytes,否则使用 none。
Accept-Ranges: bytes 3. 响应状态码 在请求成功的情况下,服务器会返回 206 Partial Content 状态码。
在请求的范围越界的情况下,服务器会返回 416 Requested Range Not Satisfiable 状态码。
在不支持范围请求的情况下,服务器会返回 200 OK 状态码。
分块传输编码 Chunked Transfer Coding,可以把数据分割成多块,让浏览器逐步显示页面。
多部分对象集合 一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔,每个部分都可以有首部字段。
例如,上传多个表单时可以使用如下方式:
Content-Type: multipart/form-data; boundary=AaB03x--AaB03xContent-Disposition: form-data; name=submit-nameLarry--AaB03xContent-Disposition: form-data; name=files; filename=file1.txtContent-Type: text/plain... contents of file1.txt ...--AaB03x--
# 三次握手
- seq:sequence number的缩写,表示所传数据的序号。TCP传输时每一个字节都有一个序号,发送数据时会将数据的第一个序号发送给对方,接收方会按序号检查是否接收完整了,如果没接收完就需要重新传送,这样就可以保证数据的完整性。
- ack:acknoledgement number的缩写,表示确认号。接收端用它来给发送端反馈已经成功接收到的数据信息,它的值为希望接收的下一个数据包起始序号,也就是ack值所代表的序号前面数据已经成功接收到了。
- ACK:确认位,只有ACK=1的时候ack才起作用。正常通信时ACK=1,第一次发起请求时因为没有需要确认接收的数据所以ACK为0。
- SYN:同步位,用于在建立连接时同步序号。刚开始简历连接时并没有历史接收的数据,所以ack就没办法设置,这时按照正常的机制就无法运行了,SYN的作用就是来解决这个问题的,当接收端接收到SYN=1的报文时就会直接将ack设置为接收到的seq+1的值,注意这里的值并不是校验后设置的,而是根据SYN直接设置的,这样正常的机制就可以运行了,所以SYN叫同步位。需要注意的是,SYN会在前两次握手时都为1,这是因为通信的双方的ack都需要设置一个初始值。
- FIN:终止位,用来在传输数据完毕后释放连接。
# https和http使用过程中的可能的问题
Mixed Content: The page at '<URL>' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint '<URL>'. This request has been blocked; the content must be served over HTTPS.
在添加了SSL证书的HTTPS中引入用HTTP的链接,就报错,请求协议的不同导致的.所有有关https带有http的文件或者链接例如:images / js等文件都会报请求
“网页的“ < url > ”是通过 HTTPS 加载的,但是请求一个不安全的帧“ < url > ”。此请求已被阻止; 内容必须通过 HTTPS 提供。”
解决方法:
- 第一种:查看http的文件,放到https路径中,引入方式添加https
- 第二种 页面的head中加入:
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">意思是自动将http的不安全请求升级为https (注意仅在需要的地方引入) 结合第一二种一起解决
客户端服务端双向通信 客户端和服务端的通信方式有很多种,大多数场景下都是由客户端主动发送数据给服务端,但在特定的场景下(如多人协作、在线游戏)客户端还需要和服务端保持实时通信,此时需要使用双向通信。
常见的双向通信方式包括 HTTP 短轮询(polling)、HTTP 长轮询(long-polling)、XHR Streaming、Server-Sent Events、Websocket 等。
其中,最简单粗暴的莫过于 HTTP 短轮询,客户端每隔特定的时间(比如 1s)便向服务端发起请求,获取最新的资源信息。该方式会造成较多的资源浪费,尤其当服务端内容更新频率低于轮询间隔时,就会造成服务端资源、客户端资源的浪费。除此之外,过于频繁的请求也会给服务端造成额外的压力,当服务端负载较高的时候,甚至可能导致雪崩等情况发生。
HTTP 长轮询解决了短轮询的一些问题,长轮询实现特点主要为当客户端向服务端发起请求后,服务端保持住连接,当数据更新响应之后才断开连接。然后客户端会重新建立连接,并继续等待新数据。此技术的主要问题在于,在重新连接过程中,页面上的数据可能会过时且不准确。
相比 HTTP 长轮询,XHR Streaming 可以维护客户端和服务端之间的连接。但使用 XHR Streaming 过程中,XMLHttpRequest对象的数量将不断增长,因此在使用过程中需要定期关闭连接,来清除缓冲区。
SSE(Server-Sent Events)方案思想便是 XHR Streaming,主要基于浏览器中EventSourceAPI 的封装和协议。它会对 HTTP 服务开启一个持久化的连接,以text/event-stream格式发送事件, 会一直保持开启直到被要求关闭。
最后我们来介绍 WebSocket,它实现了浏览器与服务端全双工通信。前面我们提到,HTTP 短轮询、长轮询都会带来额外的资源浪费,因此 Websocket 在实现实时通信的同时,能更好地节省服务端资源和带宽。
Websoctet 是如何实现全双工通信的呢?Websocket 建立在 TCP 协议之上,握手阶段采用 HTTP 协议,但这个 HTTP 协议的请求头中,有以下的标识性内容。
Connection: Upgrade、Upgrade: websocket:表示这个连接将要被转换为 WebSocket 连接。
Sec-WebSocket-Key:向服务端提供所需的信息,以确认客户端有权请求升级到 WebSocket。
Sec-WebSocket-Protocol:指定一个或多个的 WebSocket 协议。
Sec-WebSocket-Version:指定 WebSocket 的协议版本。
如果服务端同意启动 WebSocket 连接,会在握手过程中的 HTTP 协议中返回包含Sec-WebSocket-Accept的响应消息,接下来客户端和服务端便建立 WebSocket 连接,并通过 WebSocket 协议传输数据。
由于不再需要通过 HTTP 协议通信,省去请求头等内容设置,Websocket 数据格式会更加轻量,通信更加高效,性能开销也相应地降低。除此之外,不同于 HTTP 协议,Websocket 协议没有同源限制,因此客户端可以与任意服务端通信。