22023面试真题之网络篇

3,764 阅读45分钟

不去美化没有走过的那条路,高高兴兴打好手上剩下的牌

大家好,我是柒八九

今天,我们继续2023前端面试真题系列。我们来谈谈关于网络的相关知识点。

如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。

文章list

  1. 2023前端面试真题之JS篇
  2. 2023面试真题之CSS篇
  3. 2023面试真题之浏览器篇

你能所学到的知识点

  1. 缓存 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  2. HTTP头 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  3. ETag 推荐阅读指数⭐️⭐️⭐️
  4. 跨域原理与解决办法 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  5. 验证码的原理和防范原因 推荐阅读指数⭐️⭐️⭐️
  6. HTTPS 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  7. 常见的 http状态码和请求头 推荐阅读指数⭐️⭐️⭐️
  8. 用户登录是如何实现的 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  9. 前端安全知识 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  10. HTTP X.X 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  11. WebSocket协议 推荐阅读指数⭐️⭐️⭐️
  12. DNS协议 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  13. TCP协议 推荐阅读指数⭐️⭐️⭐️⭐️⭐️

好了,天不早了,干点正事哇。


缓存

缓存:何时起作用

HTTP缓存是作用于网站导航阶段的网络请求的开始阶段

  • SericeWorker之后
  • Socket 进行 DNS 查询之前

HTTP缓存策略

最好最快的请求就是没有请求

浏览器对静态资源的缓存本质上HTTP 协议的缓存策略,其中又可以分为

  1. 强制缓存
    • 根据过期时间决定使用本地缓存还是请求新资源
  2. 协商缓存
    • 每次都会发出请求,经过服务器进行对比后决定采用本地缓存还是新资源

两种缓存策略都会将资源缓存到本地

Expires 和 Cache-control:max-age=x(强缓存)

ExpiresCache-control:max-age=x强制缓存策略的关键信息,两者均是响应首部信息(后端返给客户端)的。

ExpiresHTTP 1.0 加入的特性,通过指定一个明确的时间点作为缓存资源的过期时间,在此时间点之前客户端将使用本地缓存的文件应答请求,而不会向服务器发出实体请求

Expires 的优点:

  • 可以在缓存过期时间内减少客户端的 HTTP 请求
  • 节省了客户端处理时间和提高了 Web 应用的执行速度
  • 减少了服务器负载以及客户端网络资源的消耗

Expires 一个致命的缺陷是:

  • 它所指定的时间点是以服务器为准的时间
  • 但是客户端进行过期判断时是将本地的时间与此时间点对比

如果客户端的时间与服务器存在误差,那么通过 Expires 控制的缓存资源将会失效,客户端将会发送实体请求获取对应资源。

针对这个问题, HTTP 1.1 新增了 Cache-control 首部信息以便更精准地控制缓存。

常用的 Cache-control 信息有以下几种。

  • no-cache:
    • no-cache将会和服务器进行一次通讯
  • no-store
    • no-store要求资源每次都被请求并且下载下来
  • max-age=<seconds>
    • 指定从请求的时刻开始计算,此响应的缓存副本有效的最长时间(单位:
    • 例如,max-age=360表示浏览器在接下来的 1 小时内使用此响应的本地缓存,不会发送实体请求到服务器

max-age 指定的是缓存的时间跨度,而非缓存失效的时间点,不会受到客户端与服务器时间误差的影响。

Expires 相比, max-age 可以更精确地控制缓存,并且比 Expires更高的优先级

强制缓存策略下( Cache-control 未指定 no-cacheno-store)的缓存判断流程

  • exprice.png

协商缓存

发生时机 浏览器

  1. Cache-control 没有指定 no-cache
  2. 或者 max-ageExpires 均过期之后

协商缓存是服务器用来确定缓存资源是否可用过期

因为服务器需要向浏览器确认缓存资源是否可用,二者要进行通信,而通信的过程就是发送请求,所以在header中就需要有专门的标识来让服务器确认请求资源是否可以缓存访问,所以就有了下面两组header字段:

  1. EtagIf-None-Match
  2. Last-ModifiedIf-Modified-Since

Last-Modified/If-Modify-Since

Last-Modified

当浏览器第一次向服务器请求资源时,服务器会在该资源的请求头中加上Last-ModifiedLast-Modified是该资源在服务器的最新修改时间,也就是说Last-Modified记录了该资源的最后一次创建、修改时间,保证服务器给浏览器的资源是最新的

If-Modify-Since

然后当浏览器再次请求这个资源时,会在请求报文中带上If-Modify-Since

If-Modify-Since是浏览器上一次请求该资源时返回的Last-Modified时间,也就是说Last-ModifiedIf-Modify-Since是同一个时间

当服务器收到If-Modify-Since时间后,会判断这个资源的当前最新修改时间If-Modify-Since时间是否相等,相等则说明浏览器缓存的资源已经是最新的了,服务器返回304状态码告诉浏览器资源已是最新不用更新

不相等则说明在浏览器没有请求的这段时间,这个资源已经进行了修改、更新,浏览器本地缓存的这个资源已经不是最新的了,这时候服务器重新返回该资源的最新版以及最新的Last-Modified时间


Etag/If-None-Match

Etag/If-None-MatchLast-Modified/If-Modify-Since的处理逻辑一致,通俗讲都是第一次请求给个标识,然后下一次请求再把这个标识传回去,服务器判断资源改变没有,改变了就重新返回,没有改变就返回304,浏览器继续使用本地缓存。

Etag服务器自动生成或者由开发者生成的资源在服务器的唯一标识符,能够更加准确的控制缓存

etag.png

Etag/If-None-Match的优先级高于Last-Modified/If-Modify-Since


HTTP头(8个)

GETPOST 比较

幂等

HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同

get/post 请求参数位置

针对get/post参数位置,一般来讲:

  • GET请求提交的参数体现在url中
  • POST请求的参数通过请求体提交

但是,GET请求和POST请求本质上是一样的,参数都可以通过url或者请求体来提交,但是并不能确保参数都可以收到。

在实际开发中,除非遇到特殊情况,GET请求使用url传递参数,POST请求参数通过请求体提交。


ETag

ETag是如何生成的

ETag生成结论

  1. 对于静态文件(如cssjs、图片等),ETag的生成策略是:文件大小的16进制+修改时间
  2. 对于字符串或Buffer,ETag的生成策略是:字符串/Buffer长度的16进制+对应的hash

ETag 的作用

ETag 是一个不透明的标识符Web 服务器根据 URL 上的资源的特定版本而指定。如果 URL 上的资源内容改变,一个新的不一样的 ETag 就会被生成。ETag 可以看成是资源的指纹,它们能够被快速地比较,以确定两个版本的资源是否相同。

需要注意的是 ETag 的比较只对同一个 URL 有意义 —— 不同 URL 上资源的 ETag 值可能相同也可能不同。

ETag 的语法

ETag: W/"<etag_value>"
ETag: "<etag_value>"
  • W/(可选):'W/'(大小写敏感) 表示使用弱验证器。弱验证器很容易生成,但不利于比较。
  • <etag_value>:实体标签唯一地表示所请求的资源。它们是位于双引号之间的 ASCII 字符串(如 2c-1799c10ab70 )。没有明确指定生成 ETag 值的方法。

ETag 的使用

当一个 URL 被请求,Web 服务器会返回资源和其相应的 ETag 值,它会被放置在 HTTP 响应头的 ETag 字段中:

HTTP/1.1 200 OK
Content-Length: 44
Cache-Control: max-age=10
Content-Type: application/javascript; charset=utf-8
+ ETag: W/"2c-1799c10ab70"

然后,客户端可以决定是否缓存这个资源和它的 ETag。以后,如果客户端想再次请求相同的 URL,将会发送一个包含已保存的 ETagIf-None-Match 字段的请求

GET /index.js HTTP/1.1
Host: localhost:3000
Connection: keep-alive
+ If-None-Match: W/"2c-1799c10ab70"

客户端请求之后,服务器可能会比较客户端的 ETag 和当前版本资源的 ETag。如果 ETag 值匹配,这就意味着资源没有改变,服务器便会发送回一个极短的响应,包含 HTTP “304 未修改” 的状态。304 状态码告诉客户端,它的缓存版本是最新的,可以直接使用它


跨域原理与解决办法

跨域,指的是从一个域名去请求另外一个域名的资源,即跨域名请求。跨域时,浏览器不能执行其他域名网站的脚本,这是由浏览器的同源策略造成的,是浏览器施加的安全限制, 跨域限制访问,其实是浏览器的限制

同源策略是浏览器最核心也最基本的安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源 ,这是一个用于隔离潜在恶意文件的重要安全机制.

所谓同源是指:协议域名端口相同,即两个资源具有相同的源。 只要三者之间有一个不同,就是跨域(跨源)

非同源限制

  1. 无法读取非同源网页的 CookieLocalStorageIndexedDB
  2. 无法对非同源网页的 DOMJS对象进行操作
  3. 无法向非同源地址发送 AJAX请求

实现跨域的解决方案(6种)

  1. JSONP
  2. CORS
  3. http-proxy
  4. nginx
  5. websocket
  6. 跨站脚本API访问,如:postMessagedocument.domain

JSONP

由于同源策略的限制,AJAX请求是不允许进行跨域请求的,但是HTML中,拥有srchref属性的标签是可以跨域请求外部资源的,如linkscriptimg等,根据<script>标签的特性,开发人员想到了一个解决跨域请求的方法,即JSONP,全名 JSON with padding

例如,存在如下的JSONP接口

https://www.baidu.com/sugrec
?prod=pc
&wd=什么是JSONP
&cb=getData

该接口的特点是:你输入一个指定的函数名,然后服务器会根据函数名返回一串JS的函数调用格式的字符串

服务器返回的内容,无非就是 函数名 (参数) 的格式。如果前端有一个名为getData形参是一个对象的JS函数,意味着我们可以把服务器返回的数据看成是一段调用了一个函数名为getData、形参是一个对象的函数的JS代码

那怎么让服务器返回的数据变成一段JS代码呢?在HTML页面里面编写JS代码——只需将JS代码用<script>标签括起来就可以让代码在页面加载的时候就运行了。<script>标签的src属性是可以跨域请求外部资源的,如果我们将我们要访问的接口做为src属性的值,可以访问该跨域接口了。如果我们再提供一个全局的函数getData,用来对接口返回的数据进行操作,那就实现了跨域请求了。

JSONP的原理:

  1. 使用script 标签发送请求,这个标签支持跨域访问
  2. script 标签里面给服务器端传递一个 callback
  3. callback 的值对应到页面一定要定义一个全局函数
function jsonp({url, params, cb}) {
    return new Promise((resolve, reject) => {

        let scriptDom = document.createElement('script');

        window[cb] = function (data) {
            resolve(data);
            document.body.removeChild(scriptDom);
        };
        params = {...params, cb}
        let arrs = []
        for (let key in params) {
            arrs.push(`${key}=${params[key]}`);
        }
        scriptDom.src = `${url}?${arrs.join('&')}`;

        document.body.appendChild(scriptDom);
    })
};

调用处理

jsonp({
    url: 'https://www.baidu.com/sugrec',
    params: {prod: 'pc', wd: '什么是JSONP'},
    cb: 'getData'
}).then(data => {
    console.log(data);
});

CORS

同源策略默认阻止“跨域”获取资源。但是跨域资源共享CORS 给了web服务器这样的权限:服务器可以选择是否允许跨域请求访问到它们的资源

跨域资源共享(CORS)是一种机制, 它由一系列的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应,从而克服了AJAX只能同源使用的限制。

跨域时,浏览器会让请求带上Origin请求头,表明请求来自哪个站点;
服务器必须要让响应带上允许跨域访问的Access-Control-Allow-Origin响应头,表明允许某个站点可以进行访问该服务器。

简单请求

浏览器将CORS请求分成两类:

  1. 简单请求
  2. 非简单请求
(1)HTTP请求方法是以下三种之一:
	·HEAD
	·GET
	·POST
(2)只包含简单HTTP请求头,即:
	·Accept,
	·Accept-Language,
	·Content-Language,
	·Content-Type并且值是 
        application/x-www-form-urlencoded, 
        multipart/form-data, 
        或者 text/plain之一的。

当请求满足上面的两个条件时,则该请求被视为简单请求,否则被视为非简单请求

简单请求与非简单请求的最主要区别就是跨域请求是否需要发送预检请求preflight request

在进行跨域请求时,如果是简单请求,则浏览器会在请求中增加一个Origin请求头之后直接发送CORS请求,服务器检查该请求头的值是否在服务器设置的CORS许可范围内,如果在许可范围内,则服务器同意本次请求,

如果不在许可范围内,则服务会返回一个没有包含Access-Control-Allow-Origin 响应头的HTTP响应。(最终的处理,是由浏览器决定是否用这个数据信息)

非简单请求

如果是非简单请求,则浏览器会先发起一次预检请求OPTIONS请求),浏览器除了会带上Origin请求头之外,还会再带上Access-Control-Request-MethodAccess-Control-Request-Headers 这两个请求头,服务器在收到预检请求之后,会检查这三个请求头是否与服务器的资源设置(接口)一致

只要预检请求中三个请求头有任意一个值与服务器的资源(接口)设置不一致,服务器就会拒绝预检请求,如果都一致,则服务器确认通过预检请求并返回带有Access-Control-Allow-CredentialsAccess-Control-Allow-HeadersAccess-Control-Allow-MethodsAccess-Control-Allow-OriginAccess-Control-Max-AgeAllow等响应头的相应

CORS settings attributes

HTML5 中,一些 HTML 元素提供了对 CORS 的支持,例如 <audio><img><link><script><video> 均有一个{跨域属性|crossOrigin property} ,它允许你配置元素获取数据的 CORS 请求。

非同源情况下,设置 anonymous 关键字将不会通过 cookies,客户端 SSL 证书或 HTTP 认证交换用户凭据。


HTTP-Proxy

同源策略是浏览器施加的安全限制,它只存在于浏览器中。

因此,我们可以在前端服务器与后端服务器之间加一个代理中间件(比如Node中间件)来实现,通过代理中间件转发请求,从而达到跨域请求的目的

var express = require('express');
var proxy = require('http-proxy-middleware');

var app = express();

app.use('/api', 
      proxy({
          target: 'http://localhost:3001/', 
          changeOrigin: true
          })
        );
app.listen(3000);

利用express在3000端口启动了一个小型的服务器,利用了

  • app.use('/api', proxy({target: 'http://localhost:3001/', changeOrigin: true}));

这句话,使发到3000端口/api请求转发到了3001端口。即请求http://localhost:3000/api相当于请求http://localhost:3001/api


Nginx

Nginx配置解决iconfont跨域

浏览器跨域访问jscssimg常规静态资源是被同源策略许可的,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,这些文件是不会被允许的跨域访问的,此时可在Nginx静态资源服务器中加入以下配置

location ~* \.(eot|ttf|woff|svg|otf)$ {
  add_header Access-Control-Allow-Origin *;
}
或者是:
location / {
  add_header Access-Control-Allow-Origin *;
}

Nginx反向代理接口跨域

还可以通过Nginx配置一个代理服务器来转发请求,反向代理访问后台接口,并修改cookie中域名信息,从而实现跨域携带cookie

server {
    listen  80;
    server_name  www.front.com;
    
    location / {
         #反向代理
        proxy_pass   http://www.end.com:8080;
        #将cookie里的域名修改为前端的域名
        proxy_cookie_domain www.end.com www.front.com; 
        index  index.html index.htm;

        # 如果不是浏览器直接访问Nginx时,下面的跨域配置可不启用,下面配置是为了添加响应头
        add_header Access-Control-Allow-Origin http://www.front.com;  #当前端只进行跨域不需要携带cookie时,可为*,否则不能为*,具体看后面附录补充的请求头的说明
        add_header Access-Control-Allow-Credentials true;
    }
}

WebSocket

浏览器允许脚本直连一个WebSocket地址而不遵循同源策略,所以我们可以通过使用WebSocket协议来实现跨域。具体代码如下:

var socket = io('http://www.front.com:8080');


socket.on('connect', function() {
    
    socket.on('message', function(msg) {
        console.log('data from server: ---> ' + msg); 
    });

    
    socket.on('disconnect', function() { 
        console.log('Server socket has closed.'); 
    });
});

document.getElementsByTagName('input')[0].onblur = function() {
    socket.send(this.value);
};

验证码的原理和防范原因

验证码作为一种人机识别手段,其终极目的,就是区分正常人和机器的操作。而对于没有验证码的场景,比如用户登陆,则机器可以同时、大批量的发起请求,

  • 一来系统可能扛不住
  • 二来机器可以不断变换密码来尝试破解用户的密码,实现盗号

工作原理

工作原理是:

  1. 使用random函数随机生成一个验证码,位数可以自己定(一般是4位、6位验证码)
  2. 调用短信运营商的接口,并向其发送手机号和验证码数据
  3. 短信运营平台初步审核将信息发送给三大运营商的某一家
  4. 运营商最终审核,将信息发送到用户手机上
  5. 网站或app保存接口返回的信息
  6. 服务器端会将这些信息保存在会话控制器Session中,作为后期的验证使用
  7. 网站或app接收用户填写的验证码信息
  8. 服务器端判断用户提交的验证码是否与储存在会话控制器Session中的信息一致,如果一致就通过;否则就验证失败。(也可以同时验证信息的失效时间)

HTTPS

HTTPS不是一个新的应用层协议,它其实就是 HTTP + TLS/SSL 协议组合而成,而安全性的保证正是 TLS/SSL 所做的工作。

HTTPS 就是身披了一层 SSLHTTP

探讨 HTTPS 的握手过程,其实就是 SSL/TLS 的握手过程。

TLS 旨在为 Internet 提供通信安全的加密协议。TLS 握手是启动和使用 TLS 加密的通信会话的过程。在 TLS 握手期间,Internet 中的通信双方会彼此交换信息,验证密码套件,交换会话密钥。

每当用户通过 HTTPS 导航到具体的网站并发送请求时,就会进行 TLS 握手。除此之外,每当其他任何通信使用HTTPS(包括 API 调用和在 HTTPS 上查询 DNS)时,也会发生 TLS 握手。

TLS 具体的握手过程会根据所使用的密钥交换算法的类型和双方支持的密码套件而不同

HTTPS握手过程

  1. 首先是tcp的三次握手建立连接
  2. client发送client-random+支持的加密算法集合
  3. server收到信息,返回选择一个加密算法+server-random+ 证书
  4. client验证证书有效性,并用client-random+server-random生成pre-master通过服务器公钥加密 发送给server
  5. server收到premaster,根据约定的加密算法client-random+server-random+pre-master(解密)生成master-secret,然后发送预定成功
  6. client收到生成同样的master-secert,对称加密秘钥传输完毕

常见的 http状态码和请求头

HTTP 状态码分类

HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型。响应分为五类:

  1. 信息响应(100–199)
  2. 成功响应(200–299)
  3. 重定向(300–399)
  4. 客户端错误(400–499)
  5. 服务器错误 (500–599)


消息头

消息头按照用途可分为四大类

  1. 通用头:适用于请求和响应的头字段
  2. 请求头:用于表示请求消息的附加信息的头字段
  3. 响应头:用于表示响应消息的附加信息的头字段
  4. 实体头:用于消息体的附加信息的头字段

请求头


用户登录是如何实现的

  1. 基于serversession的管理方式
  2. cookie-base的管理方式
  3. token-base的管理方式
  4. JWT

基于server端session的管理

  1. 服务端session是用户第一次访问应用时,服务器就会创建的对象,代表用户的一次会话过程,可以用来存放数据。服务器为每一个session都分配一个唯一的sessionid,以保证每个用户都有一个不同的session对象
  2. 服务器在创建完session后,会把sessionid通过cookie返回给用户所在的浏览器,这样当用户第二次及以后向服务器发送请求的时候,就会通过cookie把sessionid传回给服务器,以便服务器能够根据sessionid找到与该用户对应的session对象。
  3. session通常有失效时间的设定,比如2个小时。当失效时间到,服务器会销毁之前的session,并创建新的session返回给用户。但是只要用户在失效时间内,有发送新的请求给服务器,通常服务器都会把他对应的session的失效时间根据当前的请求时间再延长2个小时。
  4. session在一开始并不具备会话管理的作用。它只有在用户登录认证成功之后,并且往sesssion对象里面放入了用户登录成功的凭证,才能用来管理会话。管理会话的逻辑也很简单,只要拿到用户的session对象,看它里面有没有登录成功的凭证,就能判断这个用户是否已经登录。当用户主动退出的时候,会把它的session对象里的登录凭证清掉。所以在用户登录前或退出后或者session对象失效时,肯定都是拿不到需要的登录凭证的。

存在问题

  1. 这种方式将会话信息存储在web服务器里面,所以在用户同时在线量比较多时,这些会话信息会占据比较多的内存
  2. 当应用采用集群部署的时候,会遇到多台web服务器之间如何做session共享的问题
  3. 多个应用要共享session时,还会遇到跨域问题,因为不同的应用可能部署的主机不一样,需要在各个应用做好cookie跨域的处理。

cookie-base的管理方式

  1. 用户发起登录请求,服务端根据传入的用户密码之类的身份信息,验证用户是否满足登录条件,如果满足,就根据用户信息创建一个登录凭证,这个登录凭证简单来说就是一个对象,最简单的形式可以只包含用户id,凭证创建时间和过期时间三个值
  2. 服务端把上一步创建好的登录凭证,先对它做数字签名,然后再用对称加密算法做加密处理,将签名、加密后的字串,写入cookiecookie的名字必须固定(如ticket),因为后面再获取的时候,还得根据这个名字来获取cookie值。这一步添加数字签名的目的是防止登录凭证里的信息被篡改,因为一旦信息被篡改,那么下一步做签名验证的时候肯定会失败。做加密的目的,是防止cookie被别人截取的时候,无法轻易读到其中的用户信息。
  3. 用户登录后发起后续请求,服务端根据上一步存登录凭证的cookie名字,获取到相关的cookie值。然后先做解密处理,再做数字签名的认证,如果这两步都失败,说明这个登录凭证非法;如果这两步成功,接着就可以拿到原始存入的登录凭证了。然后用这个凭证的过期时间和当前时间做对比,判断凭证是否过期,如果过期,就需要用户再重新登录;如果未过期,则允许请求继续。

存在问题

  1. cookie有大小限制,存储不了太多数据
  2. 每次传送cookie,增加了请求的数量,对访问性能也有影响;
  3. 跨域问题

前面两种会话管理方式因为都用到cookie,不适合用在native app里面:native app不好管理cookie,毕竟它不是浏览器。这两种方案都不适合用来做纯api服务的登录认证

token-based的管理方式

cookie-based的方式没有太多区别,只不过cookie-based里面写到cookie里面的ticket在这种方式下称为token,这个token在返回给客户端之后,后续请求都必须通过url参数或者是http header的形式,主动带上token,这样服务端接收到请求之后就能直接从http header或者url里面取到token进行验证:

JWT

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案

JWT 的原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户。

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

JWT 的数据结构

它是一个很长的字符串,中间用点(.)分隔成三个部分 JWT 的三个部分依次如下。

  1. Header(头部)
    • Header 部分是一个 JSON 对象,描述 JWT 的元数据
    • 将上面的 JSON 对象使用 Base64URL 算法转成字符串。
  2. Payload(负载)
    • Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据
    • 这个 JSON 对象也要使用 Base64URL 算法转成字符串。
  3. Signature(签名)

JWT 的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面

Authorization: Bearer <token>

前端安全知识

  1. {跨站脚本攻击|Cross Site Scripting}
    • 允许攻击者将恶意代码植入到提供给其它用户使用的页面中
    • 原理:攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。
    • 反射型XSS:攻击者事先制作好攻击链接, 需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面
    • 存储型XSS:代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,每当有用户访问该页面的时候都会触发代码执行,这种XSS非常危险,容易造成蠕虫,大量盗窃cookie(虽然还有种DOM型XSS,但是也还是包括在存储型XSS内)。
    • DOM型XSS
  2. {跨站请求伪造|Cross-site Request Forgery}
    • 攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。
    • 防御措施:1. 阻止不明外域的访问(同源检测) 2. CSRF Token/双重Cookie验证
  3. SQL注入
    • 通过将恶意的 Sql查询或添加语句插入到应用的输入参数中,再在后台 Sql服务器上解析执行进行的攻击。
  4. {分布式拒绝服务攻击|Distributed Denial of Service}
    • 拒绝服务攻击的升级版。拒绝攻击服务顾名思义,让服务不可用
    • 攻击者不断地提出服务请求,让合法用户的请求无法及时处理,这就是 DoS 攻击。
    • 攻击者使用多台计算机或者计算机集群进行 DoS 攻击,就是 DDoS 攻击。
  5. DNS劫持
    • 为了在客户端对收到对DNS应答进行校验,出现了DNSSEC技术
    • 以阿里、腾讯等头部互联网厂商开始推出了httpDNS服务
  6. JSON 劫持

HTTP X.X

HTTP/0.9

HTTP/0.9 是于 1991 年提出的,主要用于学术交流,需求很简单——用来在网络之间传递 HTML 超文本的内容,所以被称为超文本传输协议

HTTP/0.9 的一个完整的请求流程

  • 因为 HTTP 都是基于 TCP 协议的,所以客户端先要根据 IP 地址、端口和服务器建立 TCP 连接,而建立连接的过程就是 TCP 协议三次握手的过程。
  • 建立好连接之后,会发送一个 GET 请求行的信息,如GET /index.html用来获取 index.html。
  • 服务器接收请求信息之后,读取对应的 HTML 文件,并将数据以 ASCII 字符流返回给客户端。
  • HTML 文档传输完成后,断开连接。

总的来说,当时的需求很简单,就是用来传输体积很小的 HTML 文件,所以 HTTP/0.9 的实现有以下三个特点。

  • 第一个是只有一个请求行,并没有 HTTP 请求头和请求体,因为只需要一个请求行就可以完整表达客户端的需求了。
  • 第二个是服务器也没有返回头信息,这是因为服务器端并不需要告诉客户端太多信息,只需要返回数据就可以了。
  • 第三个是返回的文件内容是以 ASCII 字符流来传输的,因为都是 HTML 格式的文件,所以使用 ASCII 字节码来传输是最合适的。

HTTP 1.0

HTTP 1.0 是在 1996 年引入的,由于在浏览器中展示的不单是 HTML 文件了,还包括了 JavaScriptCSS、图片、音频、视频等不同类型的文件。因此支持多种类型的文件下载是 HTTP/1.0 的一个核心诉求

  • HTTP 1.0 仅仅提供了最基本的认证,这时候用户名和密码还未经加密,因此很容易收到窥探。
  • HTTP/1.0 引入了请求头响应头,它们都是以为 Key-Value 形式保存的
  • HTTP 1.0 被设计用来使用短链接
    • 即每次发送数据都会经过 TCP 的三次握手和四次挥手,效率比较低。
  • HTTP 1.0 只使用 header 中的 If-Modified-SinceExpires 作为缓存失效的标准。
  • HTTP 1.0 不支持断点续传,也就是说,每次都会传送全部的页面和数据。

HTTP 1.1

HTTP 1.1 是 HTTP 1.0 开发三年后出现的,也就是 1999 年,它做出了以下方面的变化

  • HTTP 1.1 使用了摘要算法来进行身份验证
  • HTTP 1.1 默认使用长连接(持久连接)
    • 长连接就是只需一次建立就可以传输多次数据,传输完成后,只需要一次切断连接即可。
    • 长连接的连接时长可以通过请求头中的 keep-alive 来设置
    • 持久连接在 HTTP/1.1 中是默认开启
  • HTTP 1.1 中新增加了 E-tag, If-Match, If-None-Match 等缓存控制标头来控制缓存失效。
  • HTTP 1.1 支持断点续传,通过使用请求头中的 Range 来实现。

HTTP 2.0

HTTP 2.0 是 2015 年开发出来的标准,HTTP/2 的一个核心特性是使用了 多路复用技术,因此它可以 通过一个 TCP 连接来发送多个 URL 请求。多路复用技术能充分利用带宽,最大限度规避了 TCP 的慢启动所带来的问题。

  • 头部压缩
    • 由于 HTTP 1.1 经常会出现 User-AgentCookieAcceptServerRange 等字段可能会占用几百甚至几千字节,而 Body 却经常只有几十字节,所以导致头部偏重
    • HTTP 2.0 使用 HPACK 算法进行压缩。
  • 二进制格式
    • HTTP 2.0 使用了更加靠近 TCP/IP 的二进制格式
    • 而抛弃了 ASCII 码,提升了解析效率
  • 强化安全
    • HTTP2.0 一般都跑在 HTTPS 上。
  • 多路复用
    • 一个域名只使用一个 TCP 长连接来传输数据
    • 一个请求对应一个id,这样一个连接上可以有多个请求。
    • 通过引入二进制分帧层,就实现了 HTTP 的多路复用技术
  • 可以设置请求的优先级
  • 服务器推送

HTTP 3.0

HTTP/3 选择了一个折衷的方法——UDP 协议,基于 UDP 实现了类似于 TCP 的多路数据流传输可靠性等功能,我们把这套功能称为 QUIC 协议

  • 实现了类似 TCP 的流量控制、传输可靠性的功能。虽然 UDP 不提供可靠性的传输,但 QUICUDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性。
  • 集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.2 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数
  • 实现了 HTTP/2 中的多路复用功能。和 TCP 不同,QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流(如下图)。实现了数据流的单独传输,就解决了 TCP 中的问题。
  • 实现了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。

WebSocket协议

HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道

WebSocket连接是如何创建的

在客户端的使用

var url = "ws://localhost:8080/websocket/text";
var ws = new WebSocket(url);
ws.onopen = function(event) {
    console.log("websocket connection open.");
    console.log(event);
};

ws.onmessage = function(event) {
    console.log("websocket message received.")
    console.log(event.data);
};

ws.onclose = function (event) {
    console.log("websocket connection close.");
    console.log(event.code);
};

ws.onerror = function(event) {
    console.log("websocket connection error.");
    console.log(event);
};

readyState(4个)

返回当前 WebSocket 的链接状态,只读。

let readyState = socket.readyState;

返回值

  • 0 (WebSocket.CONNECTING)
    • 正在链接中
  • 1 (WebSocket.OPEN)
    • 已经链接并且可以通讯
  • 2 (WebSocket.CLOSING)
    • 连接正在关闭
  • 3 (WebSocket.CLOSED)
    • 连接已关闭或者没有链接成功

首先,WebSocket 连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求

+GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
+Upgrade: websocket
+Connection: Upgrade
Origin: http://localhost:3000
+Sec-WebSocket-Key: client-random-string
+Sec-WebSocket-Version: 13

该请求和普通的HTTP请求有几点不同:

  1. GET请求的地址不是类似/path/,而是以ws://开头的地址;
  2. 请求头Upgrade: websocketConnection: Upgrade表示这个连接将要被转换为WebSocket连接
  3. Sec-WebSocket-Key是用于标识这个连接,并非用于加密数据;
  4. Sec-WebSocket-Version指定了WebSocket的协议版本。

随后,服务器如果接受该请求,就会返回如下响应:

+HTTP/1.1 101 Switching Protocols
+Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。


WebSocket 优点

  • 较少的控制开销
    • 在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小
  • 更强的实时性
    • 由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少。
  • 保持连接状态
    • 与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。
  • 更好的二进制支持
    • WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容。
  • 可以支持扩展。
    • WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。

DNS协议:网络世界的地址簿

Socket 库提供查询 IP 地址的功能

DNS 服务器发出查询,并接收服务器返回的响应消息。对于 DNS 服务器,我们的计算机上一定有相应的 DNS 客户端,而相当于 DNS 客户端的部分称为 DNS 解析器,或者简称解析器

通过 DNS 查询 IP 地址的操作称为域名解析

解析器实际上是一段程序,它包含在操作系统的 Socket 库中。

解析器向 DNS 服务器发出查询

解析器的调用方法

调用解析器后,解析器会向 DNS 服务器发送查询消息,然后 DNS 服务器会返回响应消息。响应消息中包含查询到的 IP 地址,解析器会取出 IP 地址,并将其写入浏览器指定内存地址中。

接下来,浏览器在向 Web 服务器发送消息时,只要从该内存地址取出 IP 地址,并将它与 HTTP 请求消息一起交给操作系统就可以了。

在 Socket 库中,采用 UDP 协议,进行信息的查询。

DNS 服务器的工作步骤

DNS 服务器的基本工作就是接收来自客户端的查询消息,然后根据消息的内容返回响应

来自客户端的查询消息包含以下 3 种信息

类型描述
域名服务器、邮件服务器(邮件地址中 @ 后面的部分)的名称
ClassClass 的值永远是代表互联网的 IN
记录类型表示域名对应何种类型的记录

类型为 A 时:表示域名对应的是 IP 地址
类型为 MX 时:表示域名对应的是邮件服务器

A 是 Address 的缩写 /MX:Mail eXchange,邮件交换的缩写

DNS 服务器上事先保存有前面这 3 种信息对应的记录数据。

DNS 服务器的基本工作

例如,如果要查询 www.wl.com 这个域名对应的 IP 地址,客 户端会向 DNS 服务器发送包含以下信息的查询消息。

信息
域名 = www.wl.com
Class = IN
记录类型 = A

然后,DNS 服务器会从已有的记录中查找域名、Class 和记录类型全部匹配的记录。

DNS 服务器会从域名与 IP 地址的对照表中查找相应的记录,并返回 IP 地址

实际上还有很多其他的类型。

  • 根据 IP 地址反查域名PTR 类型
  • 查询域名相关别名CNAME类型
  • 查询 DNS 服务器 IP 地址的 NS 类型
  • 以及查询域名属性信息SOA 类型等

信息是如何在 DNS 服务器上注册

首先,DNS 服务器中的所有信息都是按照域名以分层次的结构来保存的。DNS 中的域名都是用句点来分隔的,比如 www.wl.com,这里的句点代表了不同层次之间的界限。

在域名中,越靠右的位置表示其层级越高

这种具有层次结构的域名信息会注册到 DNS 服务器中,而每个域都是作为一个整体来处理的。

于是,DNS 服务器也具有了像域名一样的层次结构,每个域的信息都存放在相应层级的 DNS 服务器中。


寻找相应的 DNS 服务器并获取 IP 地址

这里的关键在于如何找到我们要访问的 Web 服务器的信息归哪一台 DNS 服务器管

comcn 这些域(称为顶级域),它们各自负责保存下级 DNS 服务器的信息。在互联网中,comcn 的上面还有一级域,称为根域。根域不像 com、cn 那样有自己的名字,因此在一般书写域名时经常被省略,如果要明确表示根域,应该像 www.wl.com. 这样在域名的最后再加上一个句点,而这个最后的句点就代表根域。

树状的层次结构

根域的 DNS 服务器中保管着 comcn 等的 DNS 服务器的信息

除此之外还需要完成另一项工作,那就是将根域的 DNS 服务器信息保存在互联网中所有的 DNS 服务器中。客户端只要能够找到任意一台DNS 服务器,就可以通过它找到根域 DNS 服务器,然后再一路顺藤摸瓜找到位于下层的某台目标 DNS 服务器。

分配给根域 DNS 服务器的 IP 地址在全世界仅有 13 个 ,而且这些地址几乎不发生变化。

DNS 解析流程

  1. 电脑客户端会发出一个 DNS 请求,问 www.wl.com 的 IP 是啥啊,并发给本地域名服务器 (本地 DNS)。

  2. 本地 DNS 收到来自客户端的请求。然后,查找对应的记录信息。

    • 如果能找到 www.wl.com,它直接就返回 IP 地址。
    • 如果没有,本地 DNS 会去问它的根域名服务器
  3. 根 DNS 收到来自本地 DNS 的请求,发现后缀是 .com,说:“www.wl.com 啊,这个域名是由.com 区域管理,我给你它的顶级域名服务器的地址,你去问问它吧。”

  4. 本地 DNS 转向问顶级域名服务器

    • 顶级域名服务器就是大名鼎鼎的比如 .com、.net、 .org 这些一级域名
    • 负责管理二级域名,比如 wl.com,所以它能提供一条更清晰的方向
  5. 顶级域名服务器说:“我给你负责 www.wl.com 区域的权威 DNS 服务器的地址,你去问它应该能问到。”

  6. 本地 DNS 转向问权威 DNS 服务器:“www.wl.com 对应的 IP 是啥呀?”

    • wl.com 的权威 DNS 服务器,它是域名解析结果的原出处。
    • 为啥叫权威呢?就是我的域名我做主。
  7. 权威 DNS 服务器查询后将对应的 IP 地址 X.X.X.X 告诉本地 DNS。

  8. 本地 DNS 再将 IP 地址返回客户端客户端和目标建立连接

通过缓存加快 DNS 服务器的响应

如果要查询的域名和相关信息已经在缓存中,那么就可以直接返回响应,接下来的查询可以从缓存的位置开始向下进行。相比每次都从根域找起来说,缓存可以减少查询所需的时间。

这个缓存机制中有一点需要注意,那就是信息被缓存后,原本的注册信息可能会发生改变,这时缓存中的信息就有可能是不正确的。因此,DNS 服务器中保存的信息都设置有一个有效期,当缓存中的信息超过有效期后,数据就会从缓存中删除


负载均衡

内部负载均衡

例如,某个应用要访问另外一个应用,如果配置另外一个应用的 IP 地址,那么这个访问就是一对一的。但是当被访问的应用撑不住的时候,我们其实可以部署多个。但是,访问它的应用,如何在多个之间进行负载均衡?只要配置成为域名就可以了。在域名解析的时候,我们只要配置策略,这次返回第一个 IP,下次返回第二个 IP,就可以实现负载均衡了。

全局负载均衡

为了保证我们的应用高可用,往往会部署在多个机房每个地方都会有自己的 IP 地址。当用户访问某个域名的时候,这个 IP 地址可以轮询访问多个数据中心。如果一个数据中心因为某种原因挂了,只要在 DNS 服务器里面,将这个数据中心对应的 IP 地址删除,就可以实现一定的高可用


TCP

三次握手

TCP 头部信息

我们来简单介绍一下,重要字段的作用。

  • {源端口号|Source Port}/ {目标端口号|Destination Port}:
    • 用于区别主机中的不同进程
    • IP地址是用来区分不同的主机
    • 源端口号和目的端口号配合上IP首部中的源IP地址和目的IP地址就能唯一的确定一个TCP连接;
  • {分包序号|Sequence Number}:
    • 应用程序数据如果 大于 {最大分段大小|Maximum Segment Size}(MSS)就得要进行分段。
    • 这个 Sequence Number 就是记录每个封包的序号,可以让 server 重新将 TCP 的数据组合起来。
    • 主要用来解决网络报乱序的问题

  • {回应序号|Acknowledge Number}:
    • 为了确认发送端确实有收到接收端所送出的封包数据。
    • 接收端收到这个确认码时,就能够确定之前传递的封包已经被正确的收下了。
    • 回应序号应当是上次已成功收到分包序号加1
    • 用来解决不丢包的问题

  • Code{控制标识码|Control Flag}
    这个字段共有 6 个 bits ,分别代表 6 个句柄,若为 1 则为启动 (只介绍常用的)
    • SYN:若为 1,表示 client 希望双方建立同步处理
    • ACK:若为 1 代表这个封包为响应封包
    • FIN:若为 1 ,表示传送结束,所以通知对方数据传毕, 是否同意断线,只是发送者还在等待对方的响应而已

三次握手

  1. SYN
    • 客户端选择一个随机序列号 x,并发送一个 SYN 分组
  2. SYN + ACK
    • 服务器给 x 加 1
      • x + 1
    • 并选择自己的一个随机序列号
      • y
    • 然后返回响应
  3. ACK
    • 客户端给 x 和 y 加 1
      • x + 1
      • y + 1
    • 并发送握手期间的最后一个 ACK 分组

三次握手完成后,客户端与服务器之间就可以通信了。客户端可以在发送 ACK 分组之后立即发送数据,而服务器必须等接收到 ACK 分组之后才能发送数据。


滑动窗口

TCP 采用滑动窗口来管理数据发送和 ACK 号的操作。

所谓滑动窗口,就是在发送一个包之后,不等待 ACK 号返回,而是直接发送后续的一系列包

通过这种方式,就可以实现同一时间发送多个包,减少网络延迟。

其实,通过窗口,TCP 可以控制双向发送数据的速度

流量控制

流量控制是一种预防发送端过多向接收端发送数据的机制。

为实现流量控制,TCP 连接的每一方都要通告自己的接收窗口rwnd),其中包含能够保存数据的缓冲区空间大小信息

第一次建立连接时,两端都会使用自身系统的默认设置来发送 rwnd。在后面的数据交换过程中,每个 ACK 分组都会携带相应的最新 rwnd,以便两端动态调整数据流速,使之适应发送端和接收端的容量及处理能力。

慢启动 (利用可用宽带)

流量控制确实可以防止发送端向接收端过多发送数据,但却没有机制预防任何一端向潜在网络过多发送数据。换句话说,发送端和接收端在连接建立之初,谁也不知道可用带宽是多少。因此需要一个估算机制,然后还要根据网络中不断变化的条件而动态改变速度

拥塞窗口大小cwnd):发送端对从客户端接收确认(ACK)之前可以发送数据量的限制。发送端不会通告 cwnd 变量,即发送端和接收端不会交换这个值

服务器和客户端怎么确定拥塞窗口大小的最优值呢:解决方案就是慢启动:即在分组被确认后增大窗口大小,慢慢地启动

无论带宽多大,每个 TCP 连接都必须经过慢启动阶段

换句话说,应用不可能一上来就完全利用连接的最大带宽

初始拥塞窗口大小增加到一个合理值,可以减少客户端与服务器之间的往返时间


TCP队首阻塞

TCP 在不可靠的信道上实现了可靠的网络传输

每个 TCP 分组都会带着一个唯一的序列号被发出,而所有分组必须按顺序传送到接收端。如果中途有一个分组没能到达接收端,那么后续分组必须保存在接收端的 TCP 缓冲区,等待丢失的分组重发并到达接收端。这一切都发生在 TCP 层,应用程序对 TCP 重发和缓冲区中排队的分组一无所知,必须等待分组全部到达才能访问数据。在此之前,应用程序只能在通过套接字读数据时感觉到延迟交付。这种效应称为TCP {队首阻塞|Head of Line Blocking} (HOL)

TCP 队首阻塞

队首阻塞造成的延迟可以让我们的应用程序不用关心分组重排和重组,分组到达时间会存在无法预知的延迟变化。这个时间变化通常被称为抖动,也是影响应用程序性能的一个主要因素。

TCP 队首阻塞造成的延迟,也是影响应用程序性能的一个主要因素


四次挥手

  1. FIN
    • 客户端选择一个随机序列号 x,并发送一个 FIN 分组
  2. ACK
    • 服务器给 x 加 1
      • x + 1
  3. 等后端数据都传输完毕后。。。。。
  4. FIN
    • 服务器选择自己的一个随机序列号
      • y
  5. ACK
    • 客户端给 x 和 y 加 1
      • x + 1
      • y + 1
    • 并发送握手期间的最后一个 ACK 分组

注意点:

  1. 相比三次握手,四次挥手,在 server 发起的时候,是将控制标志码SYN 换成 FIN
  2. 可以看到,在第二次挥手和第三次挥手中间,有很多未发送完成的数据,其实也好理解,在 client 接收到 server 传入的 FIN 包时候,此时可能正处于某些大包数据的发送阶段,如果此时直接回复 发送端的断开操作。并且,如果 server FIN 包早于其他正常数据包到达 client。那这些本应该被 client 收录的数据,就会平白无故的丢失

为什么要四次挥手

TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。

TCP是全双工模式,这就意味着

  • 当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据
  • 当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的
  • 当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接

后记

分享是一种态度

全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。