计算机网络
评价指标:速率,带宽,吞吐量,时延等。
实际上,大多数网站性能的瓶颈都是延迟(尤其是最后一公里问题),而不是带宽!
物理层
物理层关注的是传输数据的标准,而不在于网络传输介质(光纤),单位是bit。
数据链路层
数据链路层主要负责封装网络数据包,俗称“以太网”,单位是数据帧。
网络层
IP协议
MAC地址 + IP地址,实现主机之间的通信,单位是数据包。
网络层的主要任务是把数据分割成数据包,大多数计算机网络都不能连续地传送任意长的数据。
数据包:通过网络传输的数据基本单元,包含报头和数据本身。
传输层
MAC地址 + IP地址 + 端口号, 实现应用程序之间的通信,单位是数据段。
TCP协议
TCP(Transmission Control Protocol,传输控制协议)是面向连接,可靠的传输协议,提供校验、序列号、确认、超时重传 RTT(往返时间,Round Trip Time),不提供广播和多播,而且时间延迟比较大,适用于大文件传输。
建立连接(三次握手)
-
客户端向服务器端,发送请求连接报文段,
SYN=1,seq=x; -
服务器端接收后,为该TCP连接分配缓存和变量,并向客户端返回确认报文段,
SYN=1, ACK=1, seq=y, ack=x+1; -
客户端接收后,为该 TCP 连接分配缓存和变量,给服务器端返回确认,标志着要正式开始发送数据了,
SYN=0, ACK=1, seq=x+1, ack=y+1。
为什么三次握手的第一次不能携带数据
一方面是可能造成 SYN 泛洪,另一方面是服务端要为潜在连接分配缓存和变量,会消耗资源,第三次可以携带数据。
SYN 泛洪攻击:客户端不返回确认,服务端会挂起 TCP 连接,重复发送 ACK,消耗 CPU 和内存)
TFO(TCP Fast Open,TCP 快速打开)是一种新的优化选项,在某些条件下,允许在第一个 SYN 分组中发送应用程序数据,需要客户端和服务器共同支持。
为什么不能用两次握手进行连接?
三次是最小安全次数,如果客户端没有对服务器端的起始序列号进行确认,不能保证传输的可靠性。
释放连接(四次挥手)
-
客户端向服务器端,发送释放连接报文段,
FIN=1, seq=u; -
服务器端接收后,向客户端返回确认报文段,
ACK=1, seq=v, ack=u+1;(此时,客户端不能给服务器发送信息报文,只能接收。但是服务器要是还有信息要传给服务器,仍然能传送) -
服务器端发送完数据, 发送释放连接报文段,
FIN=1, ACK=1, seq=w, ack=u+1; -
客户端接收到
FIN=1的报文之后,返回确认报文,ACK=1, seq=u+1, ack=w+1。发送完毕之后,客户端进入等待状态,等待两个 MSL(最大报文生存时间 Maximum Segment Life),TCP 连接彻底关闭。
为什么要等待两个MSL?
因为客户端最后发送的 ACK 可能丢失,服务器没有接收到就会超时重传 FIN 报文,然后客户端再重新发送 ACK 报文。
为什么连接是三次握手,关闭却是四次握手?
关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以服务器可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接。简单来说,ACK=1 和 FIN=1 一般会分开发送。
拥塞控制
-
流量控制
发送方的发送速度很快,而接收方由于忙碌、负载重或缓冲区既定而无法处理,这样就会发生严重的丢包现象。 TCP 连接的每一方都要通告自己的接收窗口(rwnd),其中包含能够保存数据的缓冲区空间大小信息。
-
慢启动
客户端与服务器之间最大可以传输(未经 ACK 确认的)数据量取 rwnd (接收窗口) 和 cwnd (拥塞窗口大小)变量中的最小值。指数增长。
-
拥塞预防
数据量达到系统配置的拥塞阈值(ssthresh)窗口,或者有分组丢失为止,此时拥塞预防算法介入,重置拥塞窗口后,按照一定的算法来增大窗口以尽量避免丢包。快恢复 cwnd = ssthresh
性能检查清单
大多数情况下,TCP 的瓶颈都是延迟,而非带宽。
- 把服务器内核升级到最新版本(Linux:3.2+)
- 确保 cwnd 大小为 10(增大TCP的初始拥塞窗口)
- 禁用空闲后的慢启动;
- 确保启动窗口缩放;
- 减少传输冗余数据;
- 压缩要传输的数据;
- 把服务器放到离用户近的地方以减少往返时间;
- 尽最大可能重用已经建立的 TCP 连接。
UDP协议
UDP(User Datagram Protocol)是无连接,不可靠的传输协议,但时间延迟小,适用于对实时性要求高的场景,如视频聊天,但是国内视频网站为了用户体验基本都是用 TCP(加载缓冲)。
-
TCP 面向连接有状态,UDP 无连接、无状态,即正式传输数据前,不需要建立可靠的连接,所以耗时较短。
-
TCP 通过重传机制保证可靠交付,UDP 不确认、不重传、无超时,网络差的情况下丢包严重。
-
TCP 通过序列号保证有序交付,UDP 不设置包序号、不重传、不会发生队首阻塞。
-
TCP 具有流量控制和拥塞控制机制,UDP 不具备,不内置客户端或网络反馈机制。
-
TCP 是点对点传播(端口相同才能通信),UDP 可以实现广播(局域网内的所有主机)、多播(主机分组),但也要指定端口。
-
TCP 面向字节流,UDP 面向数据报(IP 数据包,package 不能分片)。
UDP 的特色在于它所省略的那些功能:连接状态、握手、重发、重组、重排、拥塞控制、拥塞预防、流量控制、甚至可选的错误检测等。UDP 应用程序必须自己实现这些机制,而且每项功能都必须保证与网络中的其他主机和协议和谐共存。
会话层
负责管理建立、断开连接,数据分割等。
表示层
负责设备的固有数据格式与网络标准数据格式之间的转换,比如转换为文字、图像或音频等。
应用层
非持续连接(短连接):服务器必须为每一个请求对象建立和维护一个全新的 TCP 连接。
持续连接(长连接):TCP 连接默认不关闭,可以被多个请求复用,需要保留上下文信息,复用传输,直到没有任何数据传输或异常断开。
HTTP 是无状态协议,每个请求和响应都是完全独立的,也就是说服务器不必保存每次请求的客户端的信息。然而,很多应用又依赖于状态信息以实现会话管理、个性化、分析等功能,cookie 和 session 可以搭载部分需要重复传输的信息。
HTTP协议
URI
URI(Uniform Resource Identifier,统一资源标识符)用来唯一标识万维网上的资源,包含URN 和URL
Schema 协议(HTTP / HTTPS / FTP) + host 域名或 IP + port 端口 (80 / 443)+ path 路径 + query 参数[optional] + fragment 锚点[optional]
HTTP/1.0 VS HTTP/1.1
- 长连接
HTTP/1.0 默认使用短连接,在只请求少量数据时是优点,但是在需要长连接时是缺点。
HTTP/1.1 默认使用长连接
Connection: keep-alive(在一个 TCP 连接上传输多个 HTTP),支持断点续传。
- 请求管道
把 FIFO 队列从客户端(请求队列)迁移到服务器(响应队列),服务器可以并行处理,但只能严格串行地返回响应。
-
分块编码传输,字节范围请求
-
增强的缓存机制
HTTP/1.0
ExpiresIf-Modified-SinceHTTP/1.1
ETagIf-Unmodified-SinceIf-MatchIf-None-Match
- 新增方法
如 PUT、HEAD、OPTIONS等。
- 新增字段
host 字段,用来指定服务器的域名。
HTTP/1.X VS HTTP/2.X
- 明文传输 / 二进制
HTTP/1.X 使用明文传输,使用文本传输不安全。
HTTP/2.X 基于 HTTPS,采用二进制格式传输,更安全。
- 二进制分帧
HTTP/2.X 可以通过二进制分帧实现请求并行,每帧会标识属于哪个 HTTP 请求流,可以用于 SSE 资源(如视频)的传输。
- 多路复用
HTTP/1.X 使用长连接时,请求次序是固定的,可能会造成队头堵塞。以前的解决方案是尽量减少请求,或同时建立多个 TCP 连接,对同一个域大多数浏览器支持同时建立最多6个 TCP 连接,但是多个连接也存在 TCP 慢启动和竞争带宽的问题,所以也有域名分区的优化,不过 DNS 查询也耗时。
HTTP/2.X 是长连接,允许多路复用。因此 HTTP/1.X 中合并文件、内联资源、雪碧图、域名分片的优化方案,对于 HTTP/2.X 来说是不必要的。
HTTP/3, 一个 TCP 连接使得 TCP 阻塞的问题被放大了,因此 HTTP/3 基于UDP实现了类似于 TCP 的多路数据流、传输可靠性等功能,称为 QUIC 协议。
- 头部压缩
同一个域名下请求头的很多字段是重复,可以压缩。(HPACK 算法,哈夫曼编码,字段字典)
针对之前的数据只编码发送差异数据。
- 服务器端推送
服务端主动向浏览器推送请求相关的资源,如 HTML 资源相关的 CSS 文件资源,有别于 Web Socket 的即时数据推送。
HTTP请求报文
GET / HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*
- 请求行(方法、URL、协议版本)
方法(method)
- GET
常用于获取资源;
幂等,浏览器会主动缓存;
参数一般放在请求行的 URL 中,只能进行 URL 编码,只能接受 ASCII 字符;- POST
常用于提交数据;
浏览器不会缓存;
参数放在请求体里,比 GET 请求更安全,无编码限制。
POST 请求产生两个 TCP 数据包,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data ,服务器响应 200 ok(返回数据)- HEAD
类似于GET请求,获取资源的元信息。- PUT
- DELETE
- CONNECT
HTTP/1.1协议,建立连接隧道,用于代理服务器。- OPTIONS
获取服务器支持访问资源的方法。
也可以利用向 web 服务器发送‘*’的请求来测试服务器的功能性。如非简单 CORS 请求的预检。
fetch 发送 POST 请求的时候,总是发送 2 次,因为第一次就是 options 请求。- TRACE
追踪请求-响应的传输路径主要用于测试或诊断,
-
请求头
-
请求体
HTTP响应报文
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
<html>
<body>Hello World</body>
</html>
- 状态行(协议版本、状态码、响应状态信息)
状态码(status-code)
1xx表示处理的中间状态,还需要继续操作
- 100 continue,客户端应该继续发送请求
- 101 switching protocol
2xx表示成功状态
- 200 OK,正常返回信息
- 204 No Content,与 200 相同,但响应头后没有 body 数据。
- 206 Partial Content,表示部分内容,它的使用场景为 HTTP 分块下载和断点续传,当然也会带上相应的响应头字段
Content-Range3xx表示重定向状态
- 301 moved permanently, 永久移动,请求的网页已经永久移动到新位置(弃用以前的站点)
- 302 moved temporarily, 临时移动(暂时不可用,可能在升级)
- 304 not modified, 自从上一次请求以来,页面的内容没有改变过
4xx表示客户端错误
- 400 Bad Request,笼统的错误
- 401 Unauthorized,请求未授权
- 403 forbidden, 服务器禁止访问
- 404 Not Found, 未找到资源
- 405 Method Not Allowed
- 408 Request Timeout
5xx表示服务器错误
- 500 internal server error 最常见的服务器错误
- 501 not implement 客户端请求的功能尚不支持
- 502 Bad Gateway 代理服务器无法获取到合法响应
- 503 Service Unavailable 表示服务器暂时无法处理请求
- 505 HTTP Version Not Supported 请求使用的 HTTP 协议版本不支持
基本上,只有2xx和304的状态码,表示服务器返回是正常状态。
-
首部行
-
实体主体(entity-body)
HTTP头部
请求头、响应头、通用头和实体头。通用头和实体头在请求报文和响应报文中都可以设置,区别主要在于请求头和响应头。
- 通用头
Connection:连接管理
keep-alive/close。Cache-Control: 控制缓存。
Transfer-Encoding:报文主体的编码格式,取值
chunked表示分块传输不定长数据,基于长连接持续推送动态内容,会覆盖 Content-Length 字段。Date: 报文的创建时间。
- 请求头 (Accept 与 认证)
Host: 请求的主机名(请求过程中可能存在代理,需要加上)(HTTP/1.1规范里唯一要求必须出现的字段)。
User-Agent: 发起请求的应用程序名称。
Accept 相关字段:客户端或者代理能够处理的类型。
Authorization: 客户端提供给服务器端的认证数据。
缓存相关字段:If-None-Match, If-Modified-Since。
Range:请求的字节范围,常用于大文件数据的传输(HTTP/1.1)。
Cookie。
- 响应头部
Age:持续响应时间(从最初响应开始)。
Server: 服务器应用程序软件的名称和版本(如Apache / Nginx)。
Accept-Ranges:能支持的字节范围,常用于大文件数据的传输。
Set-Cookie。
- 实体头部(Content 与 缓存)
Allow: 可支持的HTTP请求方法;
Location: 重定向的 URL;
Content-Base: 解析实体中的相对URL使用的基础URL;
Content-Length:实体的大小,用于定长数据的传输;
Accept的对应字段:Content-Type, Content-Encoding, Content-Language
缓存相关字段: Expires, ETag, Last-Modified;
Accept字段
- 数据格式
请求头 Accept =》实体头 Content-Type。
text: text/html, text/plain, text/css 等
image: image/gif, image/jpeg, image/png 等
audio/video: audio/mpeg, video/mp4 等
application: application/json, application/javascript, application/pdf, application/octet-stream
- 压缩方式
请求头 Accept-Encoding =》实体头 Content-Encoding。
gzip: 当今最流行的压缩格式
deflate: 另外一种著名的压缩格式
br: 一种专门为 HTTP 发明的压缩算法
- 支持语言
请求头 Accept-Language =》实体头 Content-Language。
- 字符集
请求头 Accept-Charset =》实体头 Content-Type,
Content-Type: text/html; charset=utf-8。
表单提交
Content-Type: application/x-www-form-urlencoded:数据会被编码成以&分隔的键值对;字符以URL编码方式编码;
Content-Type: multipart/form-data:数据会分为多个部分,每两个部分之间通过分隔符来分隔,每部分表述均有 HTTP 头部描述子包体,如Content-Type,在最后的分隔符会加上--表示结束。
Cookie
在请求头里携带Cookie,以键值对存储。
- 容量缺陷(不超过 4KB)
- 性能缺陷(在请求静态资源里携带,导致头部体积大)
- 安全缺陷(被第三方滥用,XSS,CSRF)
在响应头里设置相关属性 Set-Cookie。
-
过期时间
Expires 和 Max-Age
-
作用域
Domain 和 Path,如果作用域不匹配,请求头就不会加上 Cookie。
-
安全性
- Secure,cookie 只能通过 HTTPS 传输。
- HttpOnly,cookie 只能通过 HTTP 传输,不能通过 JS 操作,预防 XSS 和 CSRF。
- SameSite,默认None模式下,请求头自动带上Cookie;Lax模式下,GET请求可以带上Cookie;Strict模式下,完全禁止第三方请求携带Cookie
DNS协议
DNS 协议提供的是一种主机名到 IP 地址的转换服务,就是我们常说的域名系统。
主机名.机构名.网络名.顶级域名
根域名:
.顶级域名:国家域
cn,组织域orgedu二级域名:正式给组织和个人注册使用的唯一名称
baidunju三级域名:进一步划分
mail.google.com主机名:
DNS缓存
DNS的解析顺序:浏览器缓存 =》 操作系统缓存 =》 本地 DNS 缓存 (/etc/hosts) =》本地 DNS 服务器 =》根 DNS服务器 =》顶级 DNS服务器 =》权威 DNS服务器。
负载均衡
现在的大型网站一般使用多台服务器提供服务,一个域名可能对应多个IP地址。即把用户的请求均衡地分配到不同的服务器上。当用户发起 DNS 请求时,DNS 服务器返回域名所对应的服务器 IP 地址集合,用户一般会选择排在前面的地址发送请求。
CDN(Content Delivery Network)的基本原理就是负载均衡,将缓存服务器分布到用户访问相对集中的地区或网络。
正向代理和方向代理
-
正向代理
帮助客户端访问客户端自己访问不到的服务器,然后将结果返回给客户端。
-
反向代理
服务器拿到客户端的请求,将请求转发给其他的服务器,一般用来实现服务集群的负载均衡。
FTP协议
大家都有过机房下载、上传文件的经历吧,使用的就是 FTP 协议。
SMTP协议
邮件传输协议,基于 TCP,保证交付。
浏览器通信
同源限制
同源指的是协议、域名、端口都相同,非同源即是跨域。
- 无法读取 cookie, localStorage, indexDB
- 无法操作 DOM(iframe)
- 限制 AJAX 请求(客户端是可以发送请求的,但是浏览器的主线程检测到跨域,且没有 CORS 响应头,会觉得不安全而拦截响应体,并不会发送给渲染线程)
窗口通信
- postMessage (依赖关系)
- localStorage (同源)
跨域请求
JSONP
JSONP 就是用利用script标签的跨域特性,把跨域请求包装成 script 脚本请求,可以传递参数,与服务端约定好同名的回调函数定义数据处理,当客户端收到服务端的响应后,会执行回调函数。
缺点:只支持 GET 请求,不安全,容易造成跨站脚本攻击(XSS)。
/**
* @description JSONP 跨域请求,只支持GET,XSS不安全
* @param {String} url
* @param {Object} params
* @param {Function} cb
*/
function jsonp(url, params, cb) {
return new Promise((resolve, reject) => {
window[cb] = function (data) {
resolve(data);
document.body.removeChild(script);
}
params = {...params, cb};
let script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.src = `${url}?${covertQueryParams(params)}`;
document.body.appendChild(script);
});
}
const covertQueryParams = paramsObject => {
return Object.keys(paramsObject)
.map(key =>`${encodeURIComponent(key)}=${encodeURIComponent(Object[key])}`)
.join('&');
}
window.onload = function () {
jsonp('http://example.com/ip', {}, foo);
}
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
CORS
跨域资源共享(Cross-origin resource sharing)。
简单请求(一般是表单请求):请求方法为 GET、POST 或者 HEAD;请求头的 Accept 字段只限于三个值(application/x-www-form-urlencoded、multipart/form-data、text/plain)
浏览器向服务器发出请求,在请求头里用 Origin 字段表明来源,服务器在返回的响应头添加 Access-Control-Allow-Origin 字段,如果不匹配浏览器就会拦截响应。
CORS 请求默认不发送 Cookie 和认证信息。如果通信要包含 Cookie ,首先客户端必须在请求中设置 withCredentials 为 True,其次服务端需要指定 Access-Control-Allow-Credentials 字段,同时 Access-Control-Allow-Origin 字段不能设为星号,必须指定明确的、与请求网页一致的域名。
// 响应头
Access-Control-Allow-Origin: 'www.a.com'
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: <X-Custom-Header>
非简单请求
OPTIONS请求预检,预检请求匹配成功后,可以发送允许的请求。
// 请求头
Origin: <>
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: <X-Custom-Header>
// 响应头
Access-Control-Allow-Origin: 'www.a.com'
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: <X-Custom-Header>
Access-Control-Allow-Credentials: true //允许发送 Cookie
Access-Control-Max-Age: 1728000 // 预检请求的有效期
Nginx代理
反向代理,使用与客户端同名的跳板机。
Web Socket
WebSocket 是 HTML5 开始提供的一种浏览器与服务器进行双向数据传输的网络技术,属于应用层协议,与传统的 HTTP 协议不同,它基于 TCP 传输协议,并复用 HTTP 的握手通道。
在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。(在 HTTP 中一个 Request 只能对应有一个 Response,而且这个 Response 是被动的,不能主动发起。)
Web RTC
Web Real-Time Communication(WebRTC)由一组标准、协议和 JavaScript API 组成,用于实现浏览器之间(端到端)的音频、视频及数据共享。
WebRTC 基于 UDP 协议,使得实时通信变成一种标准功能,任何 Web 应用都无需借助第三方插件和专有软件,而是通过简单的 JavaScript API 即可利用。
- MediaStream 获取音频和视频流
- RTCPeerConnection 音频和视频数据通信
- RTCDataChannel 任意应用数据通信
缓存机制
浏览器的缓存位置
- Service Worker
- Memory Cache(内存缓存,会随着进程结束而释放)
- Disk Cache(硬盘缓存,根据 HTTP 的头部字段判断哪些资源需要缓存)
- Push Cache(推送缓存)
- 以上缓存都没有匹配就会进行网络请求
强缓存
状态码:200(from cache)
不发送请求到服务器,直接从缓存中取;根据设置的过期时间,判断缓存是否过期。
- Expires
Expires 是 HTTP/1.0的东西,所以它的作用基本忽略。 服务器告诉浏览器的缓存过期时间,在过期之前,直接使用缓存数据。 虽然这种方式添加了缓存控制,节省流量,但是可能浏览器时间和服务器时间不同步。
- Cache-Control
针对浏览器和服务器时间不同步,服务器不是直接告诉浏览器过期时间,而是告诉一个相对时间
Cache-Control=10秒,意思是 10 秒内,直接使用浏览器缓存。
如果资源已经发生变更,但是未过期?
- 设置请求头: Cache-Control: no-cache, no-store, must-revalidate
- 设置版本号
- 设置文件指纹
协商缓存
状态码:304(not modified)
当强缓存不可用,发送请求到服务器,通过请求服务器来告知缓存是否可用。
- Etag & If-None-Match
为了解决文件修改时间不精确带来的问题,服务器返回资源给浏览器,并带上文件的唯一标识
ETag;先通过
Cache-Control检查缓存是否过期,如果过期,浏览器发送请求,在请求头里加上If-None-Match(等于上一次请求的ETag);服务器比较请求头的
If-None-Match和文件的ETag,如果一致就返回 304(实体为空,减少传输数据),如果不一致就返回修改后的文件和ETag。
但是ETag改变,文件不一定改变了。
比如 nginx 中的 Etag 由 last_modified 与 content_length 组成,而 last_modified 又由 mtime 组成, 当编辑文件却未更改文件内容时,mtime 也会改变,此时 Etag 改变,但是文件内容没有更改。(Hash 也是实现 ETag 的一种方式,但是计算量太大)
- Last-Modified & If-Modified-Since
服务器每次返回资源时会在响应头里告诉浏览器文件在服务器上最近的修改时间
Last-Modified;先通过
Cache-Control检查缓存是否过期,如果过期,浏览器发送请求;浏览器在请求头里加上
If-Modified-Since(等于上一次请求的Last-Modified);服务器比较请求头的
If-Modified-Since和文件的last_modified,如果一致就返回304,如果不一致就返回修改后的文件和Last-Modified。
缓存的优先级
Pragma(HTTP1.0 )> Cache-Control > Expires > ETag > Last-Modified
代理缓存
让代理服务器接管一部分的服务端HTTP缓存,客户端缓存过期后就近到代理缓存中获取,代理缓存过期了才请求源服务器,这样流量巨大的时候能明显降低源服务器的压力。
- 源服务器(在响应头的Cache-Control里设置)
private 不允许代理缓存,或 public。
must-revalidate 客户端缓存过期就去源服务器获取;proxy-revalidate 代理服务器的缓存过期后到源服务器获取。
s-maxage 限定了缓存在代理服务器中可以存放多久。
- 客户端(在请求头的Cache-Control里设置)
only-if-cached,客户端只接受代理缓存。
存储机制
cookie, sessionStorage, localStorage
-
都可以保存在浏览器端,一般都是同源的。
-
sessionStorage 是会话级别的存储(当前浏览器窗口关闭前有效),只在同一 Session 页面中可访问,会话结束(浏览器窗口关闭)后随之销毁。
-
localStorage 是持久化的本地存储,一般限制在 5MB 。
-
cookie 是会在与服务器交互时的 HTTP 请求中携带,其大小受限(每次发送同源请求都会被携带,不能太大,一般不超过 4KB,常用于存储登录信息),可设置过期时间,一般只有同源才能共享。
-
cookie 存在容量缺陷、性能缺陷和安全缺陷,客户端应该使用 Web storage API 和 IndexedDB。
SSR,可以在componentWillMount中访问localStorage吗?
不能,会跑出Undefined的错误。
原因:ssr 会在服务端执行组件的 componentWillMount 及其之前的生命周期,不会执行 componentDidMount 。当生命周期里面调用全局对象的时候,由于 window / localstorage 是浏览器的属性对象,所以在服务器端跑的时候,就会出现没有定义的错误。
解决方案:可以把这些浏览器的属性重新封装以便使用,同构和异构。
即时通讯
短轮询、长轮询:用于实现客户端和服务器端的一个即时通讯。
短轮询(Polling)
基于 HTTP 协议,由客户端触发,实现简单,占用内存与请求资源,非实时。
- 浏览器每隔一段时间向服务器发送 HTTP 请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。
- 缺点是需要不断的建立 HTTP 连接,严重浪费了服务器端和客户端的资源。当用户增加时,服务器端的压力就会变大,这是很不合理的。
长轮询(Long-Polling)
基于 HTTP 协议,由客户端触发,比短轮询解决资源,非实时。
- 首先由客户端向服务器发起请求,当服务器收到客户端发来的请求后,先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应;如果一直没有数据,则到达一定的时间限制才返回。客户端的响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
- 长轮询和短轮询相比,优点是明显减少了很多不必要的 HTTP 请求,相对节约了资源。缺点在于,连接挂起也会导致资源的浪费。
SSE
基于HTTP协议,客户端和服务端触发,实现简便,只适用于高级浏览器,有延迟,是长轮询、http stream、comet等的标准实现。
- 由客户端发起与服务器之间创建TCP连接,然后并维持这个连接,至到客户端或服务器中的做任何一放断开,连接创建后浏览器会周期性地发送消息至服务器询问,是否有自己的消息。
- HTTP stream 通过在 HTML 页面里嵌入一个隐蔵 iframe,然后将这个 iframe 的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据
- 相对于短轮询和长轮询来说,SSE(Server Sent Event)不需要建立过多的 HTTP 请求,相比之下节约了资源。
Web Socket
基于TCP协议,客户端和服务端都可以触发,开销小,安全性高,实时双向通信,配置难度高。
- WebSocket 是 HTML5 开始提供的一种浏览器与服务器进行双向数据传输的网络技术,属于应用层协议,与传统的 HTTP 协议不同,它基于 TCP 传输协议,并复用 HTTP 的握手通道。
- 使用 WebSocket 协议在服务器端的配置比较复杂。
兼容性考虑:短轮询 > 长轮询 > 长连接SSE > WebSocket
性能考虑:WebSocket > 长连接SSE > 长轮询 > 短轮询
服务端推送:WebSocket > 长连接SSE > 长轮询
页面通信
标签页之间通信
- 两个tab页面同源
利用 cookie、localStorage 等浏览器本地存储,用 setInterval 定时器监听变化。
HTML5 提供了 storage 事件,通过 Window 对象侦听 localStorage 对象的变化事件。
- 两个tab页面具有依赖关系(如 Window.open 或 iframe)
通过 HTML5 的
window.postMessageAPI完成通信。由于 postMessage 函数是绑定在 Window 全局对象下,因此通信中必须有一个页面可以获取另一个页面的 Window 对象,这样才可以完成单向通信。
- 两个tab页面不相关
- 通过在tab A和tab B中引入“桥接”功能的 iframe [bridge.html] 页面,实现双向通信。
- 使用 Web Socket,通信标签页连接同一个服务器,发送消息到服务器后,服务器推送消息给所有连接的客户端。
- 使用 Shared Worker (只在 chrome 浏览器),两个页面共享同一个线程,通过向线程发送数据和接收数据来实现标签页之间的双向通信。
登录
- Cookie + Session 登录
问题:
- 由于服务器端需要对接大量的客户端,也就需要存放大量的 SessionId,这样会导致服务器压力过大。
- 如果服务器端是一个集群,为了同步登录态,需要将 SessionId 同步到每一台机器上,无形中增加了服务器端维护成本。
- 由于 SessionId 存放在 Cookie 中,所以无法避免 CSRF 攻击。
- Token 登录
Token 是服务端生成的一串字符串,以作为客户端请求的一个令牌。当第一次登录后,服务器会生成一个 Token 并返回给客户端,客户端后续访问时,只需带上这个 Token 即可完成身份认证。
Token 并不是一串杂乱无章的字符串,而是通过多种算法拼接组合而成的字符串。最常见的 Token 生成算法是 JWT(Json Web Token),分为 header (头信息)、payload (信息体)和 signature (签名)。
特点:
- 服务端不需要存放 token,不会给服务器集群造成压力。
- Token 可以存储在前端任何地方,可以不在 cookie 里,提高了页面的安全性。
- Token 下发之后,只要在生效时间之内就一直有效,如果服务端想收回该 token 的限制并不容易。
- SSO 单点登录
单点登录指的是在公司内部搭建一个公共的认证中心,公司下的所有产品的登录都可以在认证中心里完成,一个产品在认证中心登录后,再去访问另一个产品,可以不用再次登录,即可获取登录状态。
登录态存放在 cookie 里。
- OAuth 第三方登录
主站需要登录时,引导用户重定向到第三方的登录页面,第三方系统匹配帐号成功之后,返回一个 code 到主站的回调地址。
主站接收到 code,短时间内拿着 code 请求第三方提供获取长期凭证的接口
access_token,获取之后就把access_token存到 cookie 或 localStorage 中,请求一些第三方提供的API,需要用到这个access_token作为在第三方系统的身份凭证。一些系统,在获取
access_token的时候,还会返回一个副参数refresh_token,因为access_token是有过期时间的,一旦过期了,主站可以使用refresh_token请求第三方提供的接口获取新的access_token以及新的refresh_token。
前端安全
C 机密性 Confidentiality
I 完整性 Integrity
A 可用性 Availability
XSS跨站脚本攻击
为了不和层叠样式表 CSS 混淆,故将跨站脚本攻击缩写为 XSS(Cross Site Scripting),如弹窗广告、蠕虫攻击。
- 反射型
攻击者构建了特殊的 URL(诱导用户点击恶意URL),当服务器接收到请求后,返回带有恶意代码的 HTML 文件,从而导致恶意脚本在客户端执行。对于前后端分离的项目,反射型 XSS没有用武之地。
- DOM 型
攻击者构建了特殊的 URL(诱导用户点击恶意 URL),浏览器取出参数修改 DOM,JS 可以改变 HTML 代码,取出和执行恶意代码由浏览器端完成。
- 存储型
恶意代码提交到了网站的数据库中,用户一旦访问相关页面的数据,就会导致恶意代码的执行,常见于搜索、微博、社区贴吧评论等。
预防:
- 恶意代码提交的时候(输入检查过滤,转义)
将HTML 元素内容、属性以及 URL 请求参数、CSS 值进行编码。如 escapeHTML, encodeURI (不会转义 : / ? & = 这些在 URL 中有特殊含义的字符),encodeURIComponent (只对参数的值进行转义)。
&#**;格式的字符串是html的转义字符,\是JS的转义符,转义的目的就是告诉解析器该符号为字符,而不是代码。
| 字符 | 转义后的字符 |
|---|---|
| & | & |
| < | < |
> | |
| " | " |
| ' | ' |
| / | / |
- 浏览器执行恶意代码的时候(输出检查过滤)
其根源:数据未经合适的过滤和转义,拼接到HTML,浏览器无法辨别恶意脚本。
-
避免拼接HTML:document.write()、innerHTML
-
避免执行字符串:eval()、setTimeout(字符串)、new Function(字符串)
-
避免内联监听器:onload=字符串、onerror=字符串、onclick=字符串
-
避免非法超链:javascript:、非法scheme
-
慎用内联JSON
-
HttpOnly 防止劫取 Cookie
-
不要相信用户的任何输入。 对于用户的任何输入要进行检查、过滤和转义。建立可信任的字符和 HTML 标签白名单,对于不在白名单之列的字符或者标签进行过滤或编码。
-
用户的输入会存在问题,服务端的输出也会存在问题。一般来说,除富文本的输出外,在变量输出到 HTML 页面时,可以使用编码或转义的方式来防御 XSS 攻击。例如利用 sanitize-html 对输出内容进行有规则的过滤之后再输出到页面中
CSRF跨站请求伪造
Cross-site request forgery 诱导用户进入黑客网站,利用用户的登录状态伪造请求,冒充用户在站内的正常操作
- 实现
Get请求,一般进入黑客网站后,可以通过设置img的src属性来自动发起请求。
Post请求,构造隐藏表单来自动发。
a标签的href,通过引诱链接诱惑用户点击触发请求(很常见)
- 预防
绝大多数网站是通过 cookie 等方式辨识用户身份再予以授权的,因此主要从 Cookie 角度预防,像现在谷歌和苹果都开始禁用第三方cookie。
- token 验证,强制用户当前与应用交互。
- 同源检测,根据当前请求的来源 Referer 或 Origin 来判断。
- 限制 Cookie 的使用,比如设置 cookie 的 SameSite,HttpOnly 属性。
加密算法
- 对称加密
加密和解密使用同一个密钥,但是密钥在传输过程中可能会被劫持而导致传输内容泄漏。
DES,AES(AES-128),IDEA,国密 SM1,国密 SM4
- 非对称加密
双方交换公钥,发送方使用对方的公钥 pub 对信息加密,接收方使用自己的私钥 private 对信息解密。
公钥在传输过程中,也容易受到中间人劫持并篡改。因此 CA 机构的作用是给公钥加上数字签名作为唯一标识。
私钥除了解密,也可做为数字签名。
RSA,ECC,国密SM2
- 散列算法
不可逆性,鲁棒性,唯一性。散列算法的设计:装载因子阈值,动态扩容。
MD5、SHA(SHA-256)、国密SM3
MD5 和 SHA 的唯一性被解密了,但是大部分场景下,不会构成安全问题。一般使用
SHA-256 加盐即可满足大部分使用场景。
HTTPS
HTTP,明文传输,存在被监听的风险,比如运营商劫持,默认使用80端口。
HTTPS,超文本安全传输协议(HyperText Transfer Protocol Secure),数据通信仍然是HTTP, 在 TCP 和 HTTP 中间加入了SSL(Secure Socket Layer)/ TSL(Transport Layer Security)加密数据包,默认使用443端口。
HTTPS = HTTP + 加密 + 身份验证 + 数据完整性保护。但是需要CA证书,建立握手的时间更耗时。
- 密钥传输是非对称加密,
- Client Hello
客户端发送 client-random,加密套件列表,TSL 版本。
- Server Hello
服务端返回 sever-params,server-random,使用的加密套件列表,确定 TSL 版本,CA 证书。
- Client 验证证书,生成 secret
客户端验证 CA 证书和签名,若通过验证则发送 client-params 给服务端,并从证书里提取出公钥; 基于 server-parms 和 client-params,通过公钥加密计算得到 pre-random,并通过伪随机函数计算得到最终的 secret。 CA机构和系统根证书的机制保证了HTTPS证书的公信力,避免中间人在中间过程篡改公钥。
- Server生成secret
服务端接收到客户端的 client-params,通过同样的公钥加密计算得到 pre-random,和同样的伪随机函数得到最终的 secret。
- 之后的数据传输,都通过secret作为密钥。
-
数据传输是对称加密
发起HTTP请求,接收HTTP响应,都通过非对称加密传输的 secret 作为密钥。
因为非对称加密的效率很低,而数据传输非常频繁,既可以保证传输效率,也可以保证传输的安全。