心酸历史说一下,我是nodejs小白,今天领导让我验证一下socket.io认证是否可以使用header的方式将token传给server端,网上找了很多资料都说不行,但是领导说可以所以我就写了一个例子来实现此功能。其实最终就是socket.io的websocket方式不支持header官网上有写,我所有功能都验证完了才看到。踩了好几天的坑。
demo运行前需要先安装好nodejs的运行环境。直接上代码吧。
server端 index.js
const express = require('express');
const { createServer } = require('node:http');
const { join } = require('node:path');
const { Server } = require('socket.io');
const sqlite3 = require('sqlite3');
const { open } = require('sqlite');
async function main() {
// open the database file
const db = await open({
filename: 'chat.db',
driver: sqlite3.Database
});
// create our 'messages' table (you can ignore the 'client_offset' column for now)
await db.exec(`
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_offset TEXT UNIQUE,
content TEXT
);
`);
const app = express();
const server = createServer(app);
// connectionStateRecovery是开启连接状态修复,服务端在发送消息时如果客户端断链了,会把消息临时暂存起来,等待客户端重新连接时发送给他。
// const io = new Server(server,{
// connectionStateRecovery: {} //在服务端挂掉的时候此功能不能生效
// });
const io = new Server(server, {
cors: {
allowedHeaders: ["Authorization","abcd"],
credentials: true
}
});
// 接收请求后返回前端页面 测试demo
app.get('/', (req, res) => {
console.log('来请求了 get');
res.sendFile(join(__dirname, 'index.html'));
});
// 连接成功
io.on('connection', (socket) => {
console.log('a user connected');
console.log(socket.handshake.headers);
// 加入房间
// socket.join('some room');
// 向房间内发送消息
// io.to('some room').emit('hello', 'world');
// 除房间内其他全部发送
// io.except('some room').emit('hello', 'world');
// 离开房间
// socket.leave('some room');
// chatMessage为事件, msg消息内容,callback回调数据,可作为消息接收通知。
// socket.on('chatMessage', (msg, callback) => {
// console.log('chatMessage: ' + msg);
// io.emit('chatMessage', msg);
// callback({
// status: 'ok'
// });
// });
// 连接 处理消息后保存sqlite
io.on('connection', async (socket) => {
socket.on('chatMessage', async (msg) => {
let result;
try {
// store the message in the database
result = await db.run('INSERT INTO messages (content) VALUES (?)', msg);
} catch (e) {
// TODO handle the failure
return;
}
// include the offset with the message
io.emit('chatMessage', msg, result.lastID);
});
// 判断客户端重连后查询数据库数据重发
if (!socket.recovered) {
// if the connection state recovery was not successful
try {
await db.each('SELECT id, content FROM messages WHERE id > ?',
[socket.handshake.auth.serverOffset || 0],
(_err, row) => {
socket.emit('chatMessage', row.content, row.id);
}
)
} catch (e) {
// something went wrong
}
}
});
// 断开连接
socket.on('disconnect', () => {
console.log('user disconnected');
});
// 所有事件全部接收
// socket.onAny((eventName, ...args) => {
// console.log("onAny"); // 'hello'
// console.log(eventName); // 'hello'
// console.log(args); // [ 1, '2', { 3: '4', 5: ArrayBuffer (1) [ 6 ] } ]
// });
// 所有事件全部接收,参数只接收消息的不接收回调的
socket.onAnyOutgoing((eventName, ...args) => {
console.log("onAnyOutgoing"); // 'hello'
console.log(eventName); // 'hello'
console.log(args); // [ 1, '2', { 3: '4', 5: ArrayBuffer (1) [ 6 ] } ]
});
});
server.listen(3000, () => {
console.log('server running at http://localhost:3000');
});
}
main();
客户端代码: index.html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Socket.IO chat</title>
<style>
body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
#form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
#input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
#input:focus { outline: none; }
#form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages > li { padding: 0.5rem 1rem; }
#messages > li:nth-child(odd) { background: #efefef; }
</style>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
<button id="toggle-btn">Disconnect</button>
</form>
</body>
<script>
// 消息重发1.设置超时如果服务端没有收到消息,那么10000ms后重发,一共3次。此方法不能保证消息次序。(下面还有一种方法可以实现)
// const socket = io({
// ackTimeout: 10000,
// retries: 3
// });
// socket.emit('hello', 'world');
// 使用nginx代理时设置的地址
// const socket = io("ws://10.2.180.12:6886",{
//const socket = io("ws://10.2.40.22:3000",{
const socket = io({
//transports: ["websocket"],
auth: {
serverOffset: 0
},
extraHeaders: {
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1IjoiNWZkOTU5NjE1NDU2MjQxZDRlZTY3MGJiIiwidyI6IjYxOWIwOGExNTk3OTc1MDYzMGFiMzZhYiIsInIiOiJkIiwiZXhwIjoxNzE0MTIyNDcxfQ.KzJ_JdxHF2j7oFPBEKCWbXYlHdgJLgnq",
"abcd": "abcdefg"
}
});
const toggleButton = document.getElementById('toggle-btn');
const form = document.getElementById('form');
const input = document.getElementById('input');
const messages = document.getElementById('messages');
// 监听发送按钮提交表单
form.addEventListener('submit', (e) => {
e.preventDefault();
if (input.value) {
socket.emit('chatMessage', input.value, (err, response) => {
if (err) {
console.log(err);
console.log(response);
} else {
console.log(response.status); // 'ok'
}
});
input.value = '';
}
});
//监听断开连接按钮
toggleButton.addEventListener('click', (e) => {
e.preventDefault();
if (socket.connected) {
toggleButton.innerText = 'Connect';
socket.disconnect();
} else {
toggleButton.innerText = 'Disconnect';
socket.connect();
}
});
//chatMessage事件接收
// socket.on('chatMessage', (msg) => {
// const item = document.createElement('li');
// item.textContent = msg;
// messages.appendChild(item);
// window.scrollTo(0, document.body.scrollHeight);
// });
// chatMessage事件接收,并添加offset状态
socket.on('chatMessage', (msg, serverOffset) => {
const item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
socket.auth.serverOffset = serverOffset;
});
// 消息重发2.发送事件如果服务端没有接收到,那么5000ms后在继续发
// function emit(socket, event, arg) {
// socket.timeout(5000).emit(event, arg, (err) => {
// if (err) {
// // no ack from the server, let's retry
// emit(socket, event, arg);
// }
// });
// }
// emit(socket, 'hello', 'world');
</script>
</html>
package.json文件
{
"name": "socket-chat-example",
"version": "0.0.1",
"description": "my first socket.io app",
"type": "commonjs",
"dependencies": {
"socket.io": "^4.7.5",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7"
}
}
运行:node index 即可
浏览器访问:localhost:3000
就会跳转到客户端页面。
此demo代码来自官网Getting started
有问题可以联系我一起探讨,代码我加了很多注释,希望对大家有帮助