options请求
当发送跨域的post请求时,浏览器会先发送一次options请求,这是因为浏览器的同源策略,options请求被称为预检请求,
他是跨域机制的一部分
预检请求的目的是为了确保实际请求对目标服务器是安全的,在实际请求前,浏览器向服务器发送一个预检请求,
询问服务器是否允许跨域请求以及允许哪些http方法,头部字段等,服务器通过响应信息告诉浏览器他支持哪些方法和头部字段
简单请求(get,head,post)且Content-type为application/x-www-form-urlencoded,form-data,text/plain
不会触发预检测,浏览器会直接发送实际请求
强缓存和协商缓存
强缓存: 让浏览器强制缓存服务端提供的资源(css),第二次不经过服务端,浏览器直接返回
缓存位置:硬盘缓存,内存缓存
Cache-Control: max-age=100000 Expires: 日期
协商缓存:当服务端发现资源最后修改时间和if-modified-since值相等,代表资源从该时间后未在改变过
服务端于是返回304,表示资源没有变,并且响应体为空,浏览器拿到后就知道原本可能过期的资源其实还可以继续使用,
如果资源变了,就返回200,并且响应带上最新的资源
跨域
#### jsonp跨域
原理就是通过script的src不受同源策略的限制,可以跨域请求数据,但是只能get请求
缺点:只能发送get请求,不安全,不容易维护
后端返回的是一个函数 但这个函数在前端定义的 他会把这个值注入到函数的参数里面
const jsonp = (name) => {
let script = document.createElement('script')
script.src = 'http://localhost:3000/api/jsonp?callback=' + name
document.body.appendChild(script)
return new Promise((resolve)=> {
window[name] = (data) => {
resolve(data)
}
})
}
jsonp(`callback${new date().getTime()}`).then((res)=> {
// res就是后端返回的值
console.log(res)
})
#### 前端代理 开发环境有效
server:{
proxy:{
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path)=> path.replace(/^\/api/,'')
}
}
}
#### 后端设置
app.get('api/json',(req,res)=> {
res.setHeader('Access-Control-Allow-Origin','*')
res.setHeader('Access-Control-Allow-Origin','*http://127.0.0.1:5500')
res.json({name: 'xiaoman'})
})
#### nginx代理 上线时候使用
location /api {
porxy_pass http://172.24.176.1:3000;
}
Ajax使用
// readyState 0 未初始化,XMLHttpRequest对象创建,但未调用open方法
// readyState 1 已打开,open方法已被调用,但send方法未被调用
// readyState 2 已发送,send方法已经被调用,请求已经被服务器接收
// readyState 3 正在接受,服务器正在处理并返回数据
// readyState 4 完成,服务器已经完成了数据传输
const sendAjax = () => {
const xhr = new XMLHttpRequest()
xhr.open('get', 'http://localhost:3000/api/text?name=小满',true)
xhr.timeout = 3000 // 超时
xhr.addEventListener('timeout',()=> {
console.log('请求超时')
})
xhr.addEventListener('progress', (event)=> {
console.log(event.loaded,event.total) // 当前进度和总共 可以用来做进度条
})
xhr.onreadystateChange = () => {
if(xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
// 在readyState === 4的阶段才触发load方法,可以不用onreadystateChange,使用load
xhr.addEventListener('load', ()=> {
if(xhr.status === 200) {
console.log('请求成功,触发onload')
}
})
xhr.send(null)
xhr.abort() // 请求中断
xhr.addEventListener('abort',()=> {
console.log('请求中断')
})
// post请求
xhr.open('post', 'http://localhost:3000/api/post',true)
xhr.setRequestHeader('Content-type': 'application/json')
xhr.send(JSON.stringify({name:'小满'})) // 传递json数据一定要序列化
xhr.setRequestHeader('Content-type': 'application/x-www-form-urlencoded')
xhr.send('name=小满&age=18') // urlencoded方式需要传递字符串
}
// 上传文件
<input id="file" type="file">
const file = document.getElementById('file')
file.addEventListener('click',()=> {
const formData = new FormData()
formData.append('file', file.files[0])
const xhr = new XMLHttpRequest()
xhr.open('post', 'http://localhost:3000/api/upload',true)
xhr.sendRequestHeader('Content-type': 'multipart/form-data') //这块浏览器可以自动设置,不需要手动设置
xhr.onreadystateChange = () => {
if(xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
xhr.send(formData)
})
fetch
返回类型
// text() 将响应体解析为纯文本并返回
// json() 将响应体解析为json并返回一个js对象
// blob() 将响应体解析为二进制数据并返回一个blob对象
// arrayBuffer() 将响应体解析为二进制数据并返回一个arrayBuffer对象
// formData() 将响应体解析为formdata对象
// 返回一个 Promise,resolve 时包含一个 `Response` 对象。需要进一步调用方法如 `json()`、`text()`、`blob()` 等来获取具体的响应数据类型。
fetch('http://localhost:3000/api/text').then(response=> {
console.log(response)
// 指定返回的方式
return response.text()
}).then(data=> {
console.log(data)
}).catch(error =>
console.error('Error:', error)
);
const abort = new AbortController()
fetch('http://localhost:3000/api/post',{
method: 'post',
headers: { 'Content-Type': 'application/json' },
body:JSON.stringify({name:'zhangsan'}),
signal: abort.signal // 取消请求
}).then(async res=> {
console.log(res)
// 获取当前进度
const reader = res.body.getReader() // 返回一个流
const total = res.headers.get('Content-Length')
let loaded = 0
while(true) {
// done 如果是true,说明结束了
// value 返回的是一个unit8Array
const {done,value} = await reader.read()
if(done) {
break
}
loader += value.length // 当前进度
}
// 指定返回的方式
return res.json()
})
stop.addEventListener('click', ()=> {
abort.abort()
})
SSE
SSE是一直用于实现服务器主动向客户端推送数据的技术,也被称为事件流。它基于HTTP协议,利用了其长链接特性,在客户端与服务器之间建立一个持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。
SSE和Socket的区别
SSE和WebSocket都是实现服务器向客户端实时推送数据的技术,但他们在某些方面还是有一定的区别
1.技术实现
SSE基于http,利用其长连接的特性,通过浏览器向服务器推送一个http请求,建立一个持久化连接,而webscoket是通过特殊的升级协议(http/1.1Upgrade或者HTTP2)建立的新的TCP连接,与传统的http不同
2.数据格式
SSE可以传输文本和二进制,但只支持单向数据流,既只能由服务器向客户端推送数据
Webscoket支持双向数据流,客户端和服务端可以互相发送消息,并且没有消息大小限制
3.连接状态
SSE的连接状态有三种,已连接,连接中,已断开。连接状态由浏览器自动维持,客户端无法手动关闭或重新打开连接,而socket可以手动打开关闭重连等
SSE适用场景
chatGPT,实时数据大屏展示
const eventSource = new EventSource(url,options)
EventSource.readyState 当前的状态
CONNECTING: 表示正在和服务器建立连接
OPEN: 表示已经建立连接,正在接收服务器发送的数据
CLOSED: 表示连接已经被关闭,无法接受服务器发送的数据
eventSource.close() //关闭 `EventSource` 对象与服务器的连接,停止接收服务器发送的数据
eventSource.onopen = function(event) { // 已经和服务器建立了连接,并开始接收来自服务器的数据
console.log('连接成功!', event)
}
eventSource.onerror = function(event) {
console.log('发生错误:', event)
}
// 已经接收到服务器发送的数据,当接收到数据时,触发该事件
eventSource.onmessage = function(event) {
console.log('接收到数据:', event)
}
const sse = new EventSource('http://localhost:3000/api/sse')
sse.addEventListener('open', (e) => {
console.log(e.target)
})
//对应后端nodejs自定义的事件名message(默认)
sse.addEventListener('message', (e) => { console.log(e.data) })
WebSocket
pnpm i ws
pnpm i @types/ws -D
// 后端
import ws from 'ws'
const wws = new ws.Server({port: 8080}, ()=> {
console.log('socket服务启动')
})
wss.on('conection', (socket)=> {
console.log('客户端连接成功')
socket.on('message',(e)=> {
socket.send(e.toString())
// 广播消息
wss.clients.forEach((client)=> {
client.send(JSON.stringify({
type:state.MESSAGE,
message: e.toString()
}))
})
})
})
// 前端
const ws = new WebSocket('ws://localhost:8080')
ws.addEventListener('open', function(event){
console.log('连接成功')
})
ws.addEventListener('message', function(e){
console.log(e.data)
})
const state = {
HEART:1,
MESSAGE:2
}
// socket长时间不使用,网络波动,弱网模式,他是有可能断开的
// 心跳检测 进行保活 保持连接
let heartInterval = null
const heartCheck = () => {
if(socket.readyState === ws.OPEN) {
socket.send(JSON.stringify({
tyep: state.HEART,
message: '心跳检测'
}))
}else {
clearInterval(heartInterval)
}
}
heartInterval = setInerval(heartCheck,5000)
navigator.sendBeacon
//应用场景
1.发送心跳包,以保持与服务器的长连接
2.埋点
3.发送用户反馈:可以使用navigator.sendBeacon发送用户反馈信息,如用户意见,bug
对比ajax,fecth
优点:
1.不受页面卸载过程的影响,确保数据可靠发送
2.异步执行,不阻塞页面关闭或跳转
3.能够发送跨域请求
缺点:
1.fetch和ajax都可以发送任意请求,而sendBeacon只能发送post
2.fecth和ajax可以传输任意字节数据,而sendBeacon只能传送少量数据(64kb内)
3.fecth和ajax可以定义任意请求头,而sendBeacon无法定义请求头
4.sendBeacon只能传输ArrayBuffer,ArrayBufferView,Blob,DOMString,FormData或URLSearchParams类型数据
// 后端
app.post('/api/b', (req,res)=> {
res.send('ok')
})
// 前端
let json = JSON.stringify({name:'xiaoman'})
let blob = new Blob([json],{type: 'application/json'})
send.addEventListener('click',()=> {
navigator.sendBeacon('http://localhost:3000/api/b', blob)
})