前端需要了解的网络知识

240 阅读7分钟

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协议,利用了其长链接特性,在客户端与服务器之间建立一个持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。

SSESocket的区别
SSEWebSocket都是实现服务器向客户端实时推送数据的技术,但他们在某些方面还是有一定的区别
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只能传输ArrayBufferArrayBufferViewBlobDOMStringFormDataURLSearchParams类型数据

// 后端
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)
})