1.跨域
实现方式:jsonp
,CORS
,websocket
,postMessage
jsonp
一句话阐述下JSONP原理:动态生成一个JavaScript标签,其src由接口url、请求参数、callback函数名拼接而成,利用js标签没有跨域限制的特性实现跨域请求。
Tips:1.callback函数要绑定在window对象上 2.服务端返回数据有特定格式要求:callback函数名+'('+JSON.stringify(返回数据) +')' 3.不支持post,因为js标签本身就是一个get请求
const jsonp = function(url, data) {
return new Promise((resolve, reject) => {
let dataString = url.indexOf("?") === -1 ? "?" : "&";
let callbackName = `jsonp${Date.now()}`;
url += `${dataString}callback=${callbackName}`;
if (data) {
// 如果有请求参数依次添加到url
for (let k in data) {
url += `&${k}=${data[k]}`;
}
}
let jsNode = document.createElement("script");
jsNode.src = url;
// 添加节点到document上,进行请求
document.body.appendChild(jsNode);
// 触发callback,然后删除js标签和window上的callback
window[callbackName] = result => {
delete window[callbackName];
document.body.removeChild(jsNode);
if (result) {
resolve(result);
} else {
reject(new Error("error"));
}
};
// js加载异常情况
jsNode.addEventListener("error", () => {
delete window[callbackName];
document.body.removeChild(jsNode);
reject("js标签加载失败");
});
});
};
jsonp("http://192.168.0.103:8081/jsonp", { a: 1, b: "ok" })
.then(result => {
console.log(result);
})
.catch(err => {
console.error(err);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
CORS
浏览器把CORS分为两种,有简单请求和非简单请求:
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
2
3
4
5
6
7
8
9
10
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin
字段。
如果Origin
指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest
的onerror
回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
开发
一般实际开发项目中,也是使用CORS实现代理过程,在开发环境使用webpack的Proxy
属性实现代理,部署到线上时使用Ngnix
实现反向代理:
正向代理是代理客户端,反向代理是代理服务器:
正向代理服务器位于客户端和服务器之间,为了向服务器获取数据,客户端要向代理服务器发送一个请求,并指定目标服务器,代理服务器将目标服务器返回的数据转交给客户端。
反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址。
cookie
CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials
字段。Ajax请求需要使用xhr.withCredentials = true;
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT
或DELETE
,或者Content-Type
字段的类型是application/json
。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest
请求,否则就报错。
使用WebSocket实现跨域
web sockets: 是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用) web sockets原理:在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为websocket协议。
<script>
var socket = new WebSockt('ws://www.test.com');
//http->ws; https->wss
socket.send('hello WebSockt');
socket.onmessage = function(event){
var data = event.data;
}
2
3
4
5
6
7
利用iframe实现跨域
location.hash:假设
localhost:8080
下有文件 index.html 要和localhost:8081
下的 data.html 传递消息,index.html 首先创建一个隐藏的 iframe,iframe 的 src 指向localhost:8081/data.html
,这时的 hash 值就可以做参数传递。data.html 收到消息后通过 parent.location.hash 值来修改 index.html 的 hash 值,从而达到数据传递。(Chrome 不允许修改 parent.location.hash 的值,所以要借助于localhost:8080
域名下的一个代理 iframe 的 proxy.html 页面)window.name:window.name 属性的神奇之处在于 name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。
localhost:8080index.html
在请求数据端localhost:8081/data.html
时,我们可以在该页面新建一个 iframe,该 iframe 的 src 指向数据端地址(利用 iframe 标签的跨域能力),数据端文件设置好 window.name 的值。但是由于 index.html 页面与该页面 iframe 的 src 如果不同源的话,则无法操作 iframe 里的任何东西,所以就取不到 iframe 的 name 值,所以我们需要在 data.html 加载完后重新换个 src 去指向一个同源的 html 文件,或者设置成 'about:blank;' 都行,这时候我只要在 index.html 相同目录下新建一个 proxy.html 的空页面即可。
postmessage:创建一个 iframe,使用 iframe 的一个方法 postMessage 可以向
http://localhost:8081/data.html
发送消息,然后监听 message,可以获得其文档发来的消息。
// postMessage/client/index.html 对应 localhost:8080/index.html
<iframe src="http://localhost:8081/data.html" style='display: none;'></iframe>
<script>
window.onload = function() {
let targetOrigin = 'http://localhost:8081';
window.frames[0].postMessage('index.html 的 data!', targetOrigin);
}
window.addEventListener('message', function(e) {
console.log('index.html 接收到的消息:', e.data);
});
</script>
2
3
4
5
6
7
8
9
10
11
请求数据端逻辑:
// postMessage/server/data.html 对应 localhost:8081/data.html
<script>
window.addEventListener('message', function(e) {
if(e.source != window.parent) {
return;
}
let data = e.data;
console.log('data.html 接收到的消息:', data);
parent.postMessage('data.html 的 data!', e.origin);
});
</script>
2
3
4
5
6
7
8
9
10
11
服务器代理
你访问 http://127.0.0.1:3000/topics
的时候,服务器收到请求,会代你发送请求 https://cnodejs.org/api/v1/topics
最后将获取到的数据发送给浏览器。
const url = require('url');
const http = require('http');
const https = require('https');
const server = http.createServer((req, res) => {
const path = url.parse(req.url).path.slice(1);
if(path === 'topics') {
https.get('https://cnodejs.org/api/v1/topics', (resp) => {
let data = "";
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => {
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8'
});
res.end(data);
});
})
}
}).listen(3000, '127.0.0.1');
console.log('启动服务,监听 127.0.0.1:3000');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2.原生Ajax和Fetch的区别
原生Ajax:
var xhr = new XMLHTTPRequest();
// method表示通过什么方式进行服务器访问,包括get和post;url表示访问服务器的地址;
// async表示是否异步,包括true和false(注意:true表示异步)
xhr.open(method, url, async);
// content表示向服务器发送的数据
xhr.send(content);
xhr.onreadystatechange = function() {
if (xhr.readystate == 4) {
if (xhr.status == 200) {
console.log(xhr.responseText);
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
3.GET/POST的区别
GET一般用于获取/查询信息,POST用于更新信息,本质都属于TCP链接
GET对数据长度有限制,当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的,这个限制来源于浏览器(URL 的最大长度是 2048 个字符)。POST无限制
GET后退按钮/刷新无害,POST数据会被重新提交
get请求过程:(2次交互)
- 浏览器请求tcp连接(第一次握手)
- 服务器答应进行tcp连接(第二次握手)
- 浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
- 服务器返回200 ok响应。
post请求过程:(3次交互)
- 浏览器请求tcp连接(第一次握手)
- 服务器答应进行tcp连接(第二次握手)
- 浏览器确认,并发送post请求头(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
- 服务器返回100 continue响应
- 浏览器开始发送数据
- 服务器返回200 ok响应
4.浏览器输入URL到页面加载完毕发生了什么
- 输入网址发送到DNS服务器,获取域名对应的web服务器的IP地址
- 与服务器建立TCP连接,向服务器发送http请求
- 服务器返回指定url对应的数据(200),重定向地址(301,302),或者错误信息(404)
- 浏览器下载服务器返回的数据和html源文件
- 浏览器生成DOM树,解析CSS和JS,渲染页面