node编写接口 这里用到的几个包的作用
1 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 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) let data = req.body res.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 ) => { res.writeHead(200 ,{ "Content-Type" :"text/event-stream" }) const text = fs.readFileSync("./read.txt" ,"utf8" ) const arr = text.split("" ) let current = 0 let timer = setInterval (()=> { if (current >= arr.length){ clearInterval (timer) }else { res.write(`data:${arr[current]} \n\n` ) current++ } },20 ) }) app.listen(10086 , () => { console .log("启动成功: http://localhost:10086" ) })
Ajax请求 发送GET请求 1 2 3 4 5 6 7 8 9 10 11 let xhr = new XMLHttpRequest()function sendGet ( ) { xhr.open("GET" ,"http://127.0.0.1:10086/name" ) xhr.onload = function ( ) { if (xhr.status === 200 ){ console .log(JSON .parse(xhr.responseText)) } } xhr.send() }
发送POST请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 } xhr.send(JSON .stringify(data)) }
上传文件 1 2 3 4 5 6 7 8 9 10 11 12 13 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) })
设置超时时间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function sendGet ( ) { xhr.open("GET" , "http://127.0.0.1:10086/name" ) xhr.setRequestHeader("Content-Type" , "application/json" ) xhr.timeout = 1000 xhr.addEventListener("timeout" , function ( ) { console .log("请求超时" ) }) xhr.onload = function ( ) { if (xhr.status === 200 ) { console .log(JSON .parse(xhr.responseText)) } } xhr.send() }
请求中断 1 2 3 4 5 xhr.abort() xhr.addEventListener("abort" ,function ( ) { console .log("请求中断" ) })
监听上传进度 1 2 3 4 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的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 <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请求 1 2 3 4 5 function fetchGet ( ) { fetch("http://localhost:10086/name" ).then(res => res.json()).then(res => { console .log(res) }) }
发送POST请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 构造器
1 2 <button onclick ="fetchGet()" > get请求</button > <button onclick ="stop()" > 终止请求</button >
发送请求
1 2 3 4 5 6 7 8 const abort = new AbortController()function fetchGet ( ) { fetch("http://localhost:10086/name" ,{ signal:abort.signal }).then(res => res.json()).then(res => { console .log(res) }) }
调用终止方法
1 2 3 function stop ( ) { abort.abort() }
请求超时 fetch 本身没有超时定义,需要我们自己封装一个计时器,到时间后调用 abort.abort() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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毫秒主动向前端发送一次数据
1 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 42 43 44 45 46 47 <!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 = null function start ( ) { sse = new EventSource("http://localhost:10086/ssh" ) sse.onopen = ()=> { console .log("open" ) } sse.onmessage = (event )=> { console .log(event) document .getElementById("message" ).innerHTML += event.data } sse.onerror = ()=> { console .log("onerror" ) } } function close ( ) { console .log("关闭" ) sse.close() } </script > </html >
WebSocket WebSocket是双向通信,客户端可以随时向服务端发送消息,反过来服务端也可以随时向客户端发送消息
双向通信 使用node定义接口
安装
编写接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const ws = require ("ws" )const wss = new ws.Server({port :8888 },()=> { console .log("socket服务启动成功" ) }) wss.on("connection" ,(socket )=> { console .log("客户端链接成功" ) socket.on("message" ,e => { socket.send("我是服务器,我收到你的消息了,内容是:" + e.toString()) }) })
调用
1 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 <!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.data ul.appendChild(li) }) function send ( ) { wss.send(msg.value) msg.value = "" } </script > </html >
消息广播 默认一个链接只会收到自己与服务器之间的消息,不能收到其他链接接收的消息,我们通过如下方式来实现
1 2 3 4 5 6 7 8 9 10 11 wss.on("connection" ,(socket )=> { console .log("客户端链接成功" ) socket.on("message" ,e => { wss.clients.forEach(client => { client.send("我是服务器,我收到你的消息了,内容是:" + e.toString()) }) }) })
心跳检测 在长时间没有发送消息交互,或者网络不好的情况下,websocket 会出现断掉的情况,我们可以通过心跳检测的机制,定时发送一次消息,实现链接保活
1 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 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 => { client.send(JSON .stringify({ type:STATE.MESSAGE, data:"我是服务器,我收到你的消息了,内容是:" + e.toString() })) }) }) let headInterval = null let headCheck = () => { if (socket.readyState === ws.OPEN){ socket.send(JSON .stringify({ type:STATE.HEART, data:"我是心跳包" })) }else { clearInterval (headInterval) } } setInterval (headCheck,500 ) })
同时前端需要区分不同的消息类型
1 2 3 4 5 6 7 8 9 wss.addEventListener("message" , e => { let li = document .createElement("li" ) let data = JSON .parse(e.data) if (data.type === 2 ){ li.innerText = data.data ul.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 请求 只能携带少量数据,并且不需要等待服务端响应,因此非常适合做埋点统计,以及日志统计相关功能。
使用方法 编写一个接口
1 2 3 4 5 6 const multer = require ("multer" );app.post("/ping" ,multer().none(),(req,res )=> { console .log(req.body.data) res.send("ok" ) })
前端发送 navigation.sendBeacon 请求
1 2 3 4 5 6 7 8 function send ( ) { let data = new FormData() data.append("data" , JSON .stringify({ name: "张三" , age: 1 })) navigator.sendBeacon("http://localhost:10086/ping" , data) }
后端接收到文件
浏览器相应数据
JWT鉴权 首先安装相关依赖
1 npm install jsonwebtoken express cors
jsonwebtoken:用于生成token和验证token
cors:处理跨域问题
接口编写 1 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 const express = require ("express" )const cors = require ("cors" )const jwt = require ("jsonwebtoken" )const app = express()app.use(cors()) app.use(express.json()) const KEY = "123456" const whitelist = ['/login' ];app.use((req, res, next ) => { if (whitelist.includes(req.path)) { console .log("3333" ) next(); } else { const 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.body console .log(data) if (data.name === "admin" && data.pwd === "123456" ){ res.send({ id:1 , 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
1 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 <!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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <!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 >
判断网络状态 获取是否联网 1 2 console .log(navigator.onLine)
获取网络环境 前端还可以判断用户当前所在网络的好坏
1 2 console .log(navigator.connection)
返回的是一个 NetworkInformation 对象
XSS跨站脚本攻击 随着互联网的快速普及,越来越多的敏感信息被存储在网络上,例如个人身份信息、财务信息等。在当前数字化时代,这些安全问题变得更加突出。作为开发者,我们必须采取适当的防范措施,以确保用户数据的安全性。本文将着重探讨跨站脚本攻击(Cross-site scripting,XSS)这一常见的网络攻击方式,包括其定义、原理、危害、分类和防范措施,以帮助大家更好地预防此类安全风险。
概述 定义: 跨站点脚本攻击,简称XSS,是指攻击者利用网站存在的漏洞,通过在网站中注入恶意脚本代码,从而使得用户在访问该网站时受到攻击。这些恶意脚本代码通常是JavaScript 代码,它们可以窃取用户的敏感信息,如用户名、密码等,并将这些信息发送到攻击者的服务器。
原理 XSS攻击的本质是利用Web应用程序中的漏洞,向网页注入恶意脚本代码,然后将这些代码嵌入到网页中,当其他用户访问这个网页时,恶意脚本将会被执行。
攻击者通常会在Web应用程序的输入框、评论框、搜索框等可输入内容的地方输入特定的脚本代码,这些代码可以被Web应用程序直接插入到网页中,导致网页上的所有用户都会受到攻击。
XSS攻击的原理包括以下几个步骤:
1、攻击者在Web应用程序的输入框、评论框等可输入内容的地方输入包含script标签的恶意脚本代码,例如:
2、Web应用程序将恶意脚本代码保存到数据库中或直接将其插入到网页的HTML代码中。
3、当其他用户访问这个网页时,浏览器会执行其中的恶意脚本代码。恶意脚本可以窃取用户的敏感信息,如登录凭证、浏览器历史记录、Cookie等,或者通过控制用户的浏览器来进行更多的攻击。
例如,以下是一段可以窃取用户Cookie的恶意脚本代码:
1 2 3 4 <script> let cookieValue = document .cookie;</script>
4、攻击者获取到用户的敏感信息后,可以进行更进一步的攻击,例如重定向到恶意网站、发起钓鱼攻击等。
预防工具 使用第三方库来预防,这里使用 xss,官网文档:https://www.npmjs.com/package/xss
1 2 3 4 5 6 <script src ="https://rawgit.com/leizongmin/js-xss/master/dist/xss.js" > </script > <script > var html = filterXSS('<script>alert("xss");</scr' + "ipt>" ); alert(html); </script >