1. Ajax
Ajax
: Asynchronous JavaScript
+ XML
能够向服务器请求额外数据而无须卸载页面
1.1 XMLHttpRequest
原生进行http
请求方法如下:
const request = new XMLHttpRequest()
// 以下是两种状态监听方案
// 1. 使用onreadystatechange监听XMLHttpRequest对象的状态
request.onreadystatechange = function () {
// readyState: 0: 未调用open(), 1: 调用open()未调用send()
// 2. 调用send()未收到响应, 3. 收到部分响应数据 4. 收到全部数据
if (request.readyState === 4) {
console.log('请求完成了')
} else if (request.readyState === 3) {
console.log('收到数据了')
}
}
// 2. 使用ProgressEvent对象监听XMLHttpRequest状态
// ProgressEvent对象: 测量如 HTTP 请求等底层流程进度的事件
request.onload = function (event) { // 相当于readyState为4
console.log('收到数据了:' + event.currentTarget.responseText)
}
request.onprogress = function (event) { // 相当于readyState为3
if (event.lengthComputable && event.totalSize) {
console.log('当前加载进度为' +
(event.position / event.totalSize).toFixed(2) + '%')
}
}
request.onerror = function (err) {
console.log(err)
}
request.timeout = 10000
request.ontimeout = function () {
console.log('time out')
}
request.open('GET', url, true )
request.send()
1.2 content-type
用于定义数据类型以及编码,常用的三种格式如下:
application/json
: 使用JSON
数据application/x-www-form-urlencoded
:form
表单数据被编码为key/value
格式发送到服务器multipart/form-data
: 文件传输
2. 跨域
满足以下三种条件之一,浏览器则会认为请求跨域,从而阻止该HTTP
请求
- 协议不同
- 域名不同(域名与解析后的IP不被认为是同一域名)
- 端口不同
2.1 Preflighted Request
在处理复杂跨域请求时,浏览器会在发送请求之前,使用OPTIONS
方法向服务器发送一个Preflight
请求(预请求),如果服务器响应结果为拒绝性质,则不再发送真正的请求。
进行Preflighted Request
的条件:
- 使用自定义请求头
- 使用非
GET
和POST
方法 - 使用不同类型的
content-type
2.2 解决方案
2.2.1 图像Ping
由于img
标签的加载不存在跨域,可以用来进行浏览器-服务端的单项通信
缺点:
- 只能进行
get
请求 - 无法对响应结果进行处理
const img = new Image()
img.onload = function () {
console.log('返回了')
}
// 发送数据{ name: xxx, password: xxx}
img.src = 'http://xxx.com/xxx?name=xxx&password=xxx'
2.2.2 JSONP
由于script
标签加载不存在跨域,可以用来进行浏览器-服务端的双向通信
缺点:
- 只能进行
get
请求 - 不安全
// 1. 动态添加script标签
const script = document.createElement('script')
// 2. 设置回调
script.src = 'http://www.xxx.com/xxx?callback=handleResponse'
document.body.appendChild(script)
// 3. 注入回调
function handleResponse(response) {
console.log('响应数据是:', response)
}
// 服务器响应文本
handleResponse({ name: 'soraka', age: 18})
// 服务器返回结果本质上是一串js代码,script标签加载完毕后会自动执行
// 代码的内容是执行handleResponse这个函数,传参则是服务器处理后拼接上去的字符串
// 所以在JSONP成功返回后会自动执行注入的回调并将结果传进来
2.2.3 跨域资源共享(CORS)
服务端设置Access-Control-Allow-Origin
为指定域名即可
2.2.4 代理
跨域是浏览器行为,不是HTTP
协议的部分,因此只存在客户端-服务器
间的跨域,不存在服务器-服务器
间的跨域。
webpack
和nginx
代理原理类似,解决客户端-代理服务器
间的跨域,由代理服务器转发请求至目标服务器,然后将结果返回完成请求
2.2.5 Web Socket
不再局限于客户端主动请求服务器,建立连接后,客户端可以向服务器发送消息,服务器也可以主动向客户端推送消息
// 服务器
const app = express()
var expressWs = require('express-ws')(app);
app.ws('/', function(ws, req) {
let index = 0
ws.on('message', function(msg) {
console.log(msg); // message
});
setInterval(() => {
ws.send(index++)
},1000)
});
// 客户端
const url = 'ws://192.168.x.x:5000'
const Socket = new WebSocket(url)
Socket.onmessage = function(){
console.log(event.data) // 0,1,2,3,4...
};
function send() {
Socket.send('message')
}
2.2.6 iframe类
以下方案适用于嵌套iframe
在不同域名下页面间的通信,不涉及后端
(书以及网上的理论,本人实践1,3貌似不行,可能是使用ip + port
不是域名的缘故。由于在postMessage
面前,它们都是弟弟,所以也就不太在乎可行性)
document.domain
:
主iframe
和子iframe
同时设置document.domain = 'domain.com'
,即可强制将两者设置为同一域名location.hash
:
通过hash
传值触发onhashchange
。
实现A
与B
通信,设置一个与A
同域的子域C
。A
传hash
给B
,B
收到后将处理结果传hash
给C
,C
收到后调用A
中的回调将结果传给A
window.name
:
window.name
在不同页面加载后值仍然存在,可以通过iframe
加载其他域下的页面后将值取出给当前页面使用
2.2.7 postMessage
基本使用:
- 获取接收消息的窗口的引用
- 在引用上调用
postMessage()
方法
// 示例一
// 发送方
<body>
<button onclick="send()">postMassage</button>
<iframe id="iframe" src="http://192.168.x.x:5000/public/index.html"></iframe>
<script>
const iframe = document.getElementById('iframe')
function send() {
iframe.contentWindow.postMessage('postMassage', '*')
}
</script>
</body>
// 接收方
<body>
<div id="box"></div>
<script>
window.addEventListener('message', function (event) {
const box = document.getElementById('box')
box.innerText = event.data
})
</script>
</body>
// 示例二
// 发送方
<body>
<button onclick="send()">postMassage</button>
<button onclick="openWindow()">打开</button>
<script>
let targetWindow
function openWindow() {
targetWindow = window.open('http://192.168.x.x:5000/public/index.html', 'hehe')
}
function send() {
targetWindow.postMessage('postMassage', '*')
}
</script>
</body>
// 接收方
<body>
<div id="box"></div>
<script>
window.addEventListener('message', function (event) {
const box = document.getElementById('box')
box.innerText = event.data
})
</script>
</body>
3. HTTP
网络七层协议:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
上三层注重数据的处理,下三层注重数据的传输,中间层对接上三层和下三层
3.1 TCP
传输层协议,HTTP
请求可以简单理解为TCP
的连接,HTTP
只是对数据进行了处理,本质还是TCP
3.1.1 三次握手
- 客户端:哥,我要跟你建立关系,这是我的SYN请求,序列号Seq是x
- 服务器:弟,我收到请求了,这是Ack: x + 1。我也要跟你建立关系,这是我的SYN请求,序列号Seq是y
- 客户端:哥,我收到请求了,这是Ack: y + 1
为什么三次:少一次不可靠,多一次浪费性能
3.1.2 四次挥手
- 服务器:弟,我传完了,拜拜,这是FIN请求,序列号是x
- 客户端:哥,我收到请求了,这是Ack: x + 1
- 客户端:哥,我也传完了,拜拜,这是FIN请求,序列号是y
- 服务器:弟,我收到请求了,这是Ack: y + 1
为什么四次:全双工通信,需要保证双方传输完毕
3.1.3 滑动窗口
假设一次发送三个包A,B,C
接受方: 只有在顺序收到包后才会滑动。如收到A,未收到B,收到C,窗口滑动至B处
发送方: 滑动至收到的最大确认序列包处。如未收到A、B的确认包,收到了C的确认包,窗口滑动至D处(认为A和B都收到了,只是确认包丢失了)
3.1.4 拥塞机制
慢启动: 在到达慢启动门限之前,拥塞窗口在每个RTT
之后呈指数增长
拥塞避免: 在到达慢启动门限之后,拥塞窗口在每个RTT
之后呈线性增长
快重传: 在连续三次收到一个数据包的丢失ACK后,立即重传这个数据包
快启动: 在连续三次收到一个数据包的丢失ACK后,将慢启动门限设置为拥塞窗口的一半,启动拥塞避免
3.2 HTTPS
HTTPS
是HTTP
下加入SSL
层,SSL
是HTTP
的安全基础
加密算法:
- 对称加密: 加解密使用同一个密钥,加解密速度快
- 非对称加密:公私钥一对,公钥加密只有私钥能解,私钥加密只有公钥能解,加解密速度慢
实现过程:
- 客户端向服务器发起连接请求
- 服务器返回证书和非对称加密公钥
- 客户端验证证书,使用公钥加密验证数据验证服务器
- 服务器使用私钥解密,将相同数据再加密后返回完成验证
- 客户端产生对称加密密钥,使用公钥加密后传给服务器
- 服务器解密获得密钥,此后使用对称加密进行通信
这个加密逻辑个人认为无解,第三方唯一能入手的则是在第一步就伪装成服务器,否则后续即使截获双方通信的所有内容,也无法得到有用的信息
但是验证证书又将伪装成服务器的可能扼杀,因此HTTPS
相对来说还是挺安全的
3.3 HTTP发展
http1.0
:每次请求建立一次TCP连接,请求完毕后断开连接http1.1
:建立一次TCP长连接,同步的进行多次请求,前面的请求会拥塞后面的请求http2.0
:建立一次TCP连接,并发的进行多次请求