前端基础整理 | 网络相关(二)

整理前端需要掌握的一些网络相关的基础知识和面试题。

跨域

在前后端分离的开发模式中,经常发生跨域的问题,前端发送了请求,服务器也做出了响应,但是前端却拿不到这个响应

为什么服务器做出了正确的响应,前端却拿不到这个响应呢?

因为浏览器都遵循一个同源策略(协议、主机和端口都相同,则同源)。对非同源站点,浏览器会作出一些限制:

  • 不能读取和修改对方的 DOM
  • 不读访问对方的 Cookie、IndexDB 和 LocalStorage
  • 限制 XMLHttpRequest 请求。

当浏览器向服务器发起 Ajax 请求时,如果当前的 URL 和目标的 URL不同源,则为 跨域请求

跨域请求的响应会被客户端拦截(注意:响应其实已经被客户端获取到了,只是被拦截了

处理跨域 | CORS

CORS 是 W3C 的一个标准,全称跨域资源共享。支持非 ie 以及 ie10 以上。需要服务器附加特定的响应头

浏览器根据请求方法和请求头的特定字段,将请求分为了简单请求非简单请求。针对这两类不同的请求进行不同的处理

简单请求

简单请求满足以下条件:

  • 请求方法为 GET、POST 或者 HEAD
  • 请求头的取值范围: Accept、Accept-Language、Content-Language、Content-Type(只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)

请求发出去前,浏览器在请求头中添加 Origin 字段,服务器在响应中添加 Access-Control-Allow-Origin 字段,如果这个字段不在 Origin 字段的范围中,则浏览器拦截响应。因此,Access-Control-Allow-Origin 字段是服务器用来决定浏览器是否拦截这个响应,这是必需的字段。

Access-Control-Allow-Credentials 是一个布尔值,表示是否允许发送 Cookie。浏览器对这个字段默认值设为 false,若要浏览器请求携带cookie,需要添加这个响应头并设为true, 并且在前端也需要设置withCredentials属性为 true,

1
2
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
非简单请求

除了简单请求之外的请求 (PUT DELETE PATCH等)

发起非简单请求时,会先发起预检请求(OPTIONS),同时会加上Origin源地址和Host目标地址,同时加上两个关键字段:

  • Access-Control-Request-Method, 列出 CORS 请求用到哪个HTTP方法
  • Access-Control-Request-Headers,指定 CORS 请求将要加上什么请求头

预检请求响应如下:

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0

在预检请求的响应返回后,如果请求不满足响应头的条件,则触发XMLHttpRequest的onerror方法,当然后面真正的CORS请求也不会发出去了。

处理跨域 | JSONP

原理:动态创建script标签,它可以通过 src 填上目标地址从而发出 GET 请求,实现跨域请求并拿到响应.

JSONP 与 CORS 比较

CORS相比,JSONP 最大的优势在于兼容性好,IE 低版本不能使用 CORS 但可以使用 JSONP,缺点也很明显,请求方法单一,只支持 GET 请求。

处理跨域 | Nginx

服务器进行如下配置:

1
2
3
4
5
6
7
server {
listen 80;
server_name client.com;
location /api {
proxy_pass server.com;
}
}

处理跨域 | PostMessage

MDN文档:window.postMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
otherWindow.postMessage(message, targetOrigin, [transfer]);

window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
// For Chrome, the origin property is in the event.originalEvent
// object.
// 这里不准确,chrome没有这个属性
// var origin = event.origin || event.originalEvent.origin;
var origin = event.origin
if (origin !== "http://example.org:8080")
return;

// ...
}

更详细的解释,可以参看神三元大佬的文章:https://juejin.im/post/5e76bd516fb9a07cce750746#heading-67

即时通讯的主要实现方式

即时通讯,一种基于互联网的即时交流消息的业务。。

实现即时通讯主要有四种方式:短轮询长轮询(comet)长连接(SSE)WebSocket

上述四种可分为两类:

  • 基于 HTTP:短轮询、长轮询、长连接
  • 非 HTTP:Websocket

短轮询(Polling)

基本实现思路:浏览器每隔一段时间定时向服务器发送 HTTP 请求,服务器收到请求后立即进行响应(不管内容是否发生变化)。响应完成后 TCP 连接关闭。

长轮询(Comet)

基本实现思路:当服务器收到客户端发来的请求后,不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制(服务器端设置)后关闭连接。

长轮询和短轮询比起来,减少了很多不必要的http请求次数。但是长轮询的连接一直挂起也会导致资源的浪费。

长连接(SSE)

查看 MDN文档

HTML5新增的功能,由客户端发起与服务器之间创建TCP连接,并维持这个连接,直到客户端或服务器中的任何一方断开。连接创建后,浏览器会周期性地发送消息至服务器询问
。HTTP响应内容有一种特殊的content-type —— text/event-stream,该响应头标识了响应内容为事件流,客户端不会关闭连接,而是等待服务端不断得发送响应结果。

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var source = new EventSource('/XXX');
// 默认的事件
source.addEventListener('message', function (e) {
console.log(e.data);
}, false);

// 用户自定义的事件名
source.addEventListener('my_msg', function (e) {
process(e.data);
}, false);

// 监听连接打开
source.addEventListener('open', function (e) {
console.log('open sse');
}, false);

// 监听错误
source.addEventListener('error', function (e) {
console.log('error');
});


Websocket

WebSocket是Html5定义的一个新协议,与传统的 HTTP 协议不同,可实现服务端和客户端双向同时通信(全双工通信)

首先通过 HTTP 来让客户端和服务端建立连接,连接建立后就不再使用 HTTP 协议,即可进行数据传递。

1
2
3
4
5
6
7
8
9
10
11
const socket = new WebSocket('ws://localhost:8080');

// Connection opened
socket.addEventListener('open', function (event) {
socket.send('Hello Server!');
});

// Listen for messages
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
});

CDN

全称 content delivery network,内容分发网络。是一组分布在多个不同的地理位置的服务器。根据用户的实际位置,从离用户最近的 CDN 服务器为用户提供内容,提高访问速度,提升用户体验。

主要用来解决网络拥堵,提高访问速度,解决由于网络带宽小,用户访问量大,网点分布不均等原因导致的访问速度慢的问题。

CDN的大致原理:

  1. 用户在浏览器输入要访问的域名;

  2. 对域名进行解析,由于CDN对域名解析过程进行了调整,所以解析函数库一般得到的是该域名对应的CNAME记录,为了得到实际的IP地址,浏览器需要再次对获得的CNAME域名进行解析以得到实际的IP地址;在此过程中,使用的全局负载均衡DNS解析。如根据地理位置信息解析对应的IP地址,使得用户能就近访问;

  3. 此次解析得到CDN缓存服务器的IP地址,浏览器在得到实际的ip地址之后,向缓存服务器发出访问请求;

  4. 缓存服务器根据浏览器提供的要访问的域名,通过Cache内部专用DNS解析得到此域名的实际IP地址,再由缓存服务器向此实际IP地址提交访问请求;

  5. 缓存服务器从实际IP地址得到内容以后,一方面在本地进行保存,以备以后使用,二方面把获取的数据放回给客户端,完成数据服务过程;

  6. 客户端得到由缓存服务器返回的数据以后显示出来并完成整个浏览的数据请求过程。