网络请求
node编写接口
这里用到的几个包的作用
- express:基于 Node.js 平台,快速、开放、极简的 Web 开发框架,官网:https://www.expressjs.com.cn/
- cors:用来解决跨域问题
- body-parser:可以通过 req.body 获取post请求传递过来的json数据
- multer:用于文件上传
const express = require('express');
const fs = require("fs");
const path = require("path")
const app = express()
const multer = require("multer");
const cors = require("cors")
const bodyParser = require("body-parser")
app.use(cors())
app.use(bodyParser.json())
// 设置上传目录和文件名
const storage = multer.diskStorage({destination: function (req, file, cb) {cb(null, 'uploads/')},filename: function (req, file, cb) {console.log(file)cb(null, Date.now() + path.extname(file.originalname))}
})const upload = multer({ storage: storage });app.get("/name",(req,res)=>{res.send({code:200,msg:"成功"})
})app.post("/add",(req,res)=>{console.log(req.body)// 使用req.body接收前端传递过来的post参数let data = req.bodyres.send({code:200,data:data})
})app.post("/upload", upload.single("file"), (req, res) => {if (!req.file) {return res.status(400).send('No file uploaded.');}// 输出文件信息console.log('Uploaded: ' + req.file.filename);res.send({code:200,data:'文件上传成功'})
});app.get("/sse", (req, res) => {// 声明链接方式是sseres.writeHead(200,{"Content-Type":"text/event-stream"})const text = fs.readFileSync("./read.txt","utf8")const arr = text.split("")let current = 0// 设置每隔20毫秒主动发送一次数据let timer = setInterval(()=>{if(current >= arr.length){clearInterval(timer)}else{// 发送消息必须是 data: 开头,\n\n 结尾。中间是发送的内容,内容必须是一个字符串res.write(`data:${arr[current]}\n\n`)current++}},20)
})app.listen(10086, () =>{console.log("启动成功: http://localhost:10086")
})
Ajax请求
发送GET请求
let xhr = new XMLHttpRequest()
function sendGet(){xhr.open("GET","http://127.0.0.1:10086/name")// 当接口相应数据后会触发onload回调xhr.onload = function (){if(xhr.status === 200){console.log(JSON.parse(xhr.responseText))}}xhr.send()
}
发送POST请求
let xhr = new XMLHttpRequest()
function sendPost(){xhr.open("POST","http://127.0.0.1:10086/add")xhr.setRequestHeader("Content-Type","application/json")xhr.onload = function (){if(xhr.status === 200){console.log(JSON.parse(xhr.responseText))}}// 定义发送给后端的数据let data = {name:"张三",age:18}// 把数据放在send中进行发送,需要使用JSON.stringify进行序列化xhr.send(JSON.stringify(data))
}
上传文件
var fileEl = document.getElementById("file");
fileEl.addEventListener("change",event=>{let file = event.target.files[0]xhr.open("POST","http://127.0.0.1:10086/upload")xhr.onload = function (){if(xhr.status === 200){console.log(JSON.parse(xhr.responseText))}}let data = new FormData()data.append("file",file)xhr.send(data)
})
设置超时时间
function sendGet() {xhr.open("GET", "http://127.0.0.1:10086/name")xhr.setRequestHeader("Content-Type", "application/json")// 设置超时时间1000毫秒xhr.timeout = 1000xhr.addEventListener("timeout", function () {console.log("请求超时")})xhr.onload = function () {if (xhr.status === 200) {console.log(JSON.parse(xhr.responseText))}}xhr.send()
}
请求中断
xhr.abort()
// 请求中断回调
xhr.addEventListener("abort",function (){console.log("请求中断")
})
监听上传进度
// 监听上传进度
xhr.addEventListener("progress",function (event){console.log(`${(event.loaded / event.total * 100).toFixed(2)}%`)
})
Axios使用
Axios 是对 Ajax 的封装,可以帮助我们更好的发送请求,官方文档:http://www.axios-js.com/zh-cn/docs/
下面是分别发送get和post的示例
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>function axiosGet(){axios.get("http://127.0.0.1:10086/name").then(res=>{console.log(res.data)})
}function axiosPost(){axios.post("http://127.0.0.1:10086/add",{name:"张三"}).then(res=>{console.log(res.data)})
}
fetch请求
Fetch是一种网络通信协议,用于在客户端和服务器之间传输数据。该协议使用HTTP请求和响应进行通信,与传统的AJAX方式相比,Fetch更加简单易用,并提供了许多现代化的功能。
使用Fetch可以方便地向服务器发送请求,并将响应返回给客户端。你可以使用Fetch获取文本、JSON、图像和文件等数据,并进行各种处理。Fetch还支持流式传输和取消请求等高级功能,使得处理大型数据集和长时间运行的操作变得更加简单和可靠。
Fetch API也是Javascript中常用的API之一,它提供了一组方法和属性,可以在浏览器端与服务器进行通信。通过Fetch API,你可以轻松地使用Fetch协议进行数据传输,并对请求和响应进行操作和处理。
fetch 对比 xhr
fetch
和 XMLHttpRequest
(XHR)都是前端与服务器进行数据交互的常用方式,它们各有优缺点,下面是它们的比较:
- API 设计和使用方式
fetch
的 API 设计更加现代化、简洁和易于使用,使用起来更加直观和方便。相比之下,XHR 的 API 设计比较繁琐,需要进行多个参数的配置和回调函数的处理。
- 支持的请求方法
fetch
API 默认只支持 GET 和 POST 请求方法,而 XHR 则支持所有标准的 HTTP 请求方法。
- 请求头部
在 fetch
中设置请求头部的方式更加清晰和直接,可以通过 Headers
对象进行设置,而 XHR 的方式相对较为繁琐。
- 请求体
在发送 POST 请求时,fetch
API 要求将请求体数据作为参数传递给 fetch
方法中的 options
对象,而 XHR 可以直接在 send()
方法中设置请求体数据。
- 支持的数据类型
在解析响应数据时,fetch
API 提供了多种方法,包括 .json()
, .blob()
, .arrayBuffer()
等,而 XHR 只支持文本和二进制数据两种数据类型。
- 跨域请求
在进行跨域请求时,fetch
API 提供了一种简单而强大的解决方案——使用 CORS(跨域资源共享)头部实现跨域请求,而 XHR 则使用了一个叫做 XMLHttpRequest Level 2
的规范,在代码编写上相对较为繁琐。
总的来说,fetch
API 与 XHR 各有优缺点,具体选择哪种方式还需要根据具体情况进行考虑。平时开发中使用较多的是 fetch
,因为它使用方便、API 简洁、语法清晰,同时也支持了大多数常用的功能,可以有效地简化前端开发流程。
fetch 返回格式
- text(): 将响应体解析为纯文本字符串并返回。
- json(): 将响应体解析为JSON格式并返回一个JavaScript对象。
- blob(): 将响应体解析为二进制数据并返回一个Blob对象。
- arrayBuffer(): 将响应体解析为二进制数据并返回一个ArrayBuffer对象。
- formData(): 将响应体解析为FormData对象。
发送GET请求
function fetchGet() {fetch("http://localhost:10086/name").then(res => res.json()).then(res => {console.log(res)})
}
发送POST请求
function fetchPost() {fetch("http://localhost:10086/add", {method: "post",headers:{'Content-Type':'application/json'},body: JSON.stringify({name: "张三",age: 18})}).then(res => res.json()).then(res => {console.log(res)})
}
终止请求
需要使用 AbortController 构造器
<button onclick="fetchGet()">get请求</button>
<button onclick="stop()">终止请求</button>
发送请求
const abort = new AbortController()
function fetchGet() {fetch("http://localhost:10086/name",{signal:abort.signal}).then(res => res.json()).then(res => {console.log(res)})
}
调用终止方法
function stop(){abort.abort()
}
请求超时
fetch 本身没有超时定义,需要我们自己封装一个计时器,到时间后调用 abort.abort() 方法
const abort = new AbortController()
function fetchGet() {setTimeOutFn()fetch("http://localhost:10086/name",{signal:abort.signal}).then(res => res.json()).then(res => {console.log(res)})
}function setTimeOutFn(time = 2000){setTimeout(()=>{abort.abort()},time)
}
SSE
概述
SSE(Server-Sent Events)是一种用于实现服务器主动向客户端推送数据的技术,也被称为“事件流”(Event Stream)。它基于 HTTP 协议,利用了其长连接特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。
SSE 和 Socket 区别
SSE(Server-Sent Events)和 WebSocket 都是实现服务器向客户端实时推送数据的技术,但它们在某些方面还是有一定的区别。
- 技术实现
SSE 基于 HTTP 协议,利用了其长连接特性,通过浏览器向服务器发送一个 HTTP 请求,建立一条持久化的连接。而 WebSocket 则是通过特殊的升级协议(HTTP/1.1 Upgrade 或者 HTTP/2)建立新的 TCP 连接,与传统 HTTP 连接不同。
- 数据格式
SSE 可以传输文本和二进制格式的数据,但只支持单向数据流,即只能由服务器向客户端推送数据。WebSocket 支持双向数据流,客户端和服务器可以互相发送消息,并且没有消息大小限制。
- 连接状态
SSE 的连接状态仅有三种:已连接、连接中、已断开。连接状态是由浏览器自动维护的,客户端无法手动关闭或重新打开连接。而 WebSocket 连接的状态更灵活,可以手动打开、关闭、重连等。
- 兼容性
SSE 是标准的 Web API,可以在大部分现代浏览器和移动设备上使用。但如果需要兼容老版本的浏览器(如 IE6/7/8),则需要使用 polyfill 库进行兼容。而 WebSocket 在一些老版本 Android 手机上可能存在兼容性问题,需要使用一些特殊的 API 进行处理。
- 安全性
SSE 的实现比较简单,都是基于 HTTP 协议的,与普通的 Web 应用没有太大差异,因此风险相对较低。WebSocket 则需要通过额外的安全措施(如 SSL/TLS 加密)来确保数据传输的安全性,避免被窃听和篡改,否则可能会带来安全隐患。
总体来说,SSE 和 WebSocket 都有各自的优缺点,适用于不同的场景和需求。如果只需要服务器向客户端单向推送数据,并且应用在前端的浏览器环境中,则 SSE 是一个更加轻量级、易于实现和维护的选择。而如果需要双向传输数据、支持自定义协议、或者在更加复杂的网络环境中应用,则 WebSocket 可能更加适合。
适用于场景
chatGPT 返回的数据 就是使用的SSE 技术
实时数据大屏 如果只是需要展示 实时的数据可以使用SSE技术 而不是非要使用webSocket
使用
调用node端写好的 sse 接口,这个接口会每隔20毫秒主动向前端发送一次数据
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<style>.content{width: 400px;height: 400px;border: 1px solid #ddd;}
</style>
<body><div class="content" id="message"></div><button onclick="start()">开始</button><button onclick="close()">关闭</button>
</body>
<script>let sse = nullfunction start(){sse = new EventSource("http://localhost:10086/ssh")// 链接成功回调sse.onopen = ()=>{console.log("open")}// 监听消息sse.onmessage = (event)=>{console.log(event)// 默认监听message,这个可以定义// 默认数据也是在event的data属性中返回document.getElementById("message").innerHTML += event.data}// 链接失败回调sse.onerror = ()=>{console.log("onerror")}}function close(){console.log("关闭")sse.close()}
</script>
</html>
WebSocket
WebSocket是双向通信,客户端可以随时向服务端发送消息,反过来服务端也可以随时向客户端发送消息
双向通信
使用node定义接口
安装
npm install ws
编写接口
const ws = require("ws")// 创建服务
const wss = new ws.Server({port:8888},()=>{console.log("socket服务启动成功")
})// 监听链接成功提示
wss.on("connection",(socket)=>{console.log("客户端链接成功")socket.on("message",e=>{// 通过e.toString()获取前端传递过来的东西socket.send("我是服务器,我收到你的消息了,内容是:"+ e.toString())})
})
调用
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<input type="text" id="msg"/>
<button onclick="send()">发送</button>
<ul id="ul"></ul>
</body>
<script>let msg = document.querySelector("#msg")let ul = document.querySelector("#ul")// 链接let wss = new WebSocket("ws://localhost:8888")// 监听消息wss.addEventListener("message", e => {console.log(e.data)let li = document.createElement("li")li.innerText = e.dataul.appendChild(li)})function send() {// 发送消息wss.send(msg.value)msg.value = ""}
</script>
</html>
消息广播
默认一个链接只会收到自己与服务器之间的消息,不能收到其他链接接收的消息,我们通过如下方式来实现
// 监听链接成功提示
wss.on("connection",(socket)=>{console.log("客户端链接成功")socket.on("message",e=>{// 广播消息 wss.clients 存放了所有链接信息,遍历这个链接信息,给所有链接者发送消息wss.clients.forEach(client=>{// 通过e.toString()获取前端传递过来的东西client.send("我是服务器,我收到你的消息了,内容是:"+ e.toString())})})
})
心跳检测
在长时间没有发送消息交互,或者网络不好的情况下,websocket 会出现断掉的情况,我们可以通过心跳检测的机制,定时发送一次消息,实现链接保活
const ws = require("ws")// 创建服务
const wss = new ws.Server({port: 8888}, () => {console.log("socket服务启动成功")
})// 消息类型
const STATE = {HEART:1,MESSAGE:2
}// 监听链接成功提示
wss.on("connection", (socket) => {console.log("客户端链接成功")socket.on("message", e => {// 广播消息wss.clients.forEach(client => {// 通过e.toString()获取前端传递过来的东西client.send(JSON.stringify({type:STATE.MESSAGE,data:"我是服务器,我收到你的消息了,内容是:" + e.toString()}))})})// 添加心跳检测let headInterval = nulllet headCheck = () => {if(socket.readyState === ws.OPEN){socket.send(JSON.stringify({type:STATE.HEART,data:"我是心跳包"}))}else{clearInterval(headInterval)}}// 每隔500毫秒检测一次setInterval(headCheck,500)
})
同时前端需要区分不同的消息类型
// 监听消息
wss.addEventListener("message", e => {let li = document.createElement("li")let data = JSON.parse(e.data)if(data.type === 2){li.innerText = data.dataul.appendChild(li)}
})
navigator.sendBeacon
使用 navigator.sendBeacon
实现高效的数据上报
在 web 开发中,我们经常需要将用户行为或性能数据上报到服务器。为了不影响用户体验,开发者通常会在页面卸载时进行数据上报。然而,传统的数据上报方式,如 XMLHttpRequest
或 Fetch API
,容易受到页面卸载过程中的阻塞,导致数据丢失。为了解决这个问题,navigator.sendBeacon
API 被引入,它可以在页面卸载时安全、可靠地发送数据。
navigator.sendBeacon
对比 Ajax fetch
优点
- 不受页面卸载过程的影响,确保数据可靠发送。
- 异步执行,不阻塞页面关闭或跳转。
- 能够发送跨域请求。
缺点
- fetch 和 ajax 都可以发送任意请求 而 sendBeacon 只能发送POST
- fetch 和 ajax 可以传输任意字节数据 而 sendBeacon 只能传送少量数据(64KB 以内)
- fetch 和 ajax 可以定义任意请求头 而 sendBeacon 无法自定义请求头
- sendBeacon 只能传输
ArrayBuffer
、ArrayBufferView
、Blob
、DOMString
、FormData
或URLSearchParams
类型的数据 - 如果处于危险的网络环境,或者开启了广告屏蔽插件 此请求将无效
navigator.sendBeacon
应用场景
- 发送心跳包:可以使用
navigator.sendBeacon
发送心跳包,以保持与服务器的长连接,避免因为长时间没有网络请求而导致连接被关闭。 - 埋点:可以使用
navigator.sendBeacon
在页面关闭或卸载时记录用户在线时间,pv uv,以及错误日志上报 按钮点击次数。 - 发送用户反馈:可以使用
navigator.sendBeacon
发送用户反馈信息,如用户意见、bug 报告等,以便进行产品优化和改进
其他注意事项 type
ping请求 是html5 新增的 并且是sendBeacon 特有的 ping 请求 只能携带少量数据,并且不需要等待服务端响应,因此非常适合做埋点统计,以及日志统计相关功能。
使用方法
编写一个接口
const multer = require("multer");app.post("/ping",multer().none(),(req,res)=>{console.log(req.body.data)res.send("ok")
})
前端发送 navigation.sendBeacon 请求
function send() {let data = new FormData()data.append("data", JSON.stringify({name: "张三",age: 1}))navigator.sendBeacon("http://localhost:10086/ping", data)
}
后端接收到文件
浏览器相应数据
JWT鉴权
首先安装相关依赖
npm install jsonwebtoken express cors
- jsonwebtoken:用于生成token和验证token
- cors:处理跨域问题
接口编写
const express = require("express")
const cors = require("cors")
const jwt = require("jsonwebtoken")
const app = express()// 处理跨域问题
app.use(cors())
// 可以使用 req.body 获取post请求传递的json数据
app.use(express.json())
// token加盐
const KEY = "123456"// 白名单,包含不需要验证token的路径
const whitelist = ['/login'];
// 验证token的中间件
app.use((req, res, next) => {// 检查路径是否在白名单中if (whitelist.includes(req.path)) {console.log("3333")// 在白名单中,直接进入下一个中间件或请求处理函数next();} else {// 不在白名单中,需要验证tokenconst token = req.headers['authorization'];jwt.verify(token, KEY, (err, decode) =>{if(err){res.status(401).send('无效的token')}else{next()}})}
});// 登录接口
app.post("/login", (req, res) => {let data = req.bodyconsole.log(data)if(data.name === "admin" && data.pwd === "123456"){res.send({id:1,// 生成token,第一个参数是token携带的内容,第二是KEY,第三个是过期时间token:jwt.sign({id:1,name:data.name}, KEY, {expiresIn: "1h"})})}else{res.send({code:500,msg:"用户名或密码错误"})}
})// 获取集合信息
app.get("/list",(req,res)=>{res.send([{name:"lisi",age:1},{name:"wangwu",age:2},])
})app.listen(3000, () => {console.log("服务启动成功:http://localhost:3000")
})
前端调用
前端在发送请求时需要在请求头中携带token
login.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div>用户名: <input placeholder="用户名" id="name"/>密码: <input placeholder="密码" id="pwd"/><button onclick="login()">登录</button>
</div>
<script>function login() {let name = document.getElementById("name").value;let pwd = document.getElementById("pwd").value;axios.post("http://localhost:3000/login",{name,pwd}).then(res=>{if(res.data.token){alert("登录成功")localStorage.setItem("token",res.data.token)location.href = "./list.html"}else{alert("登录失败")localStorage.removeItem("token")}})}
</script>
</body>
</html>
list.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<button onclick="list()">获取list</button>
</body>
<script>function list(){axios.get("http://localhost:3000/list",{headers: {'authorization': localStorage.getItem("token"),}}).then(res=>{console.log(res.data)})}
</script>
</html>
判断网络状态
获取是否联网
// 是否联网,返回true或者false,true表示已经联网,false表示没有联网
console.log(navigator.onLine)
获取网络环境
前端还可以判断用户当前所在网络的好坏
// 获取网络环境
console.log(navigator.connection)
返回的是一个 NetworkInformation 对象
XSS跨站脚本攻击
随着互联网的快速普及,越来越多的敏感信息被存储在网络上,例如个人身份信息、财务信息等。在当前数字化时代,这些安全问题变得更加突出。作为开发者,我们必须采取适当的防范措施,以确保用户数据的安全性。本文将着重探讨跨站脚本攻击(Cross-site scripting,XSS)这一常见的网络攻击方式,包括其定义、原理、危害、分类和防范措施,以帮助大家更好地预防此类安全风险。
概述
**定义:**跨站点脚本攻击,简称XSS,是指攻击者利用网站存在的漏洞,通过在网站中注入恶意脚本代码,从而使得用户在访问该网站时受到攻击。这些恶意脚本代码通常是JavaScript 代码,它们可以窃取用户的敏感信息,如用户名、密码等,并将这些信息发送到攻击者的服务器。
原理
XSS攻击的本质是利用Web应用程序中的漏洞,向网页注入恶意脚本代码,然后将这些代码嵌入到网页中,当其他用户访问这个网页时,恶意脚本将会被执行。
攻击者通常会在Web应用程序的输入框、评论框、搜索框等可输入内容的地方输入特定的脚本代码,这些代码可以被Web应用程序直接插入到网页中,导致网页上的所有用户都会受到攻击。
XSS攻击的原理包括以下几个步骤:
1、攻击者在Web应用程序的输入框、评论框等可输入内容的地方输入包含script标签的恶意脚本代码,例如:
<script>
// 在这里插入恶意脚本代码
</script>
2、Web应用程序将恶意脚本代码保存到数据库中或直接将其插入到网页的HTML代码中。
3、当其他用户访问这个网页时,浏览器会执行其中的恶意脚本代码。恶意脚本可以窃取用户的敏感信息,如登录凭证、浏览器历史记录、Cookie等,或者通过控制用户的浏览器来进行更多的攻击。
例如,以下是一段可以窃取用户Cookie的恶意脚本代码:
<script>
let cookieValue = document.cookie;
// 将cookieValue发送到攻击者的服务器
</script>
4、攻击者获取到用户的敏感信息后,可以进行更进一步的攻击,例如重定向到恶意网站、发起钓鱼攻击等。
预防工具
使用第三方库来预防,这里使用 xss,官网文档:https://www.npmjs.com/package/xss
<script src="https://rawgit.com/leizongmin/js-xss/master/dist/xss.js"></script>
<script>// apply function filterXSS in the same wayvar html = filterXSS('<script>alert("xss");</scr' + "ipt>");alert(html);
</script>