WebSocket连接建立后立即断开排查与保活,及Token连接实现
Nuxt3/Vue WebSocket连接异常及Token携带方案
问题概况
开发Nuxt3/Vue前端应用时,WebSocket连接建立后立刻断开,但相同连接参数在Postman中能正常运行。同时需要明确如何携带已有Token建立WebSocket连接。
相关代码
组件代码
<template> <div class="flex items-center space-x-4"> <div class="flex-1"> <input v-model="socketUrl" placeholder="WebSocket URL" class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-300 dark:bg-gray-800 dark:text-white" :disabled="isConnected" /> </div> <button @click="toggleConnection" class="px-4 py-2 text-white rounded-md focus:outline-none dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-2 dark:focus:ring-green-500" :class="[ isConnected ? 'bg-red-500 hover:bg-red-600 focus:ring-2 focus:ring-red-500' : 'bg-green-700 hover:bg-green-800 focus:ring-2 focus:ring-green-300', 'dark:bg-green-800', ]" > {{ isConnected ? "Disconnect" : "Connect" }} </button> </div> </template> <script setup> const isConnected = ref(false); const config = useRuntimeConfig(); const socketUrl = config.public.WS_URL; onMounted(() => { connectWebSocket(); startPing(); }); const toggleConnection = () => { if (isConnected.value) { disconnectWebSocket(); } else { connectWebSocket(); } isConnected.value = !isConnected.value; }; </script>
composables/socketConnection.js代码
import { ref, onMounted } from "vue"; const config = useRuntimeConfig(); const socketUrl = config.public.WS_URL; const clientId = config.public.WS_CLIENT_ID; const clientSecret = config.public.WS_CLIENT_SECRET; const webSocket = ref(null); const isConnected = ref(false); export const connectWebSocket = () => { const completeSocketURL = socketUrl + "&Authorization=Bearer "; console.log(completeSocketURL); console.log(localStorage.getItem("keycloakToken")) const socket = new WebSocket(completeSocketURL + localStorage.getItem("keycloakToken")); socket.onopen = function (event) { isConnected.value = true; console.log("Connection Opened : " + JSON.stringify(event, null, 4)); }; socket.onmessage = function (event) { console.log("WS Received Message : " + JSON.stringify(event, null, 4)); }; socket.onerror = function (event) { console.error("WS Error Received : " + JSON.stringify(event, null, 4)); }; socket.onclose = function (event) { isConnected.value = false; console.log("WS Disconnected : " + JSON.stringify(event, null, 4)); }; webSocket.value = socket; }; export const disconnectWebSocket = () => { if (webSocket.value) { webSocket.value.close(); isConnected.value = false; } }; export const startPing = () => { const pingInterval = setInterval(() => { if (webSocket.value && webSocket.value.readyState === WebSocket.OPEN) { webSocket.value.send("ping"); } }, 30000); webSocket.value.onclose = function (event) { isConnected.value = false; console.log("WS Disconnected : " + JSON.stringify(event, null, 4)); clearInterval(pingInterval); }; };
问题排查与修复
1. 连接立即断开的核心原因及解决
问题点:
- 重复覆盖
onclose事件:startPing中直接赋值webSocket.value.onclose,覆盖了connectWebSocket里的原有逻辑,导致连接状态更新混乱。 - URL参数拼接错误:如果原
socketUrl没有?起始的参数,用&拼接Authorization会导致参数无效,服务器验证失败后断开连接。 - Ping启动时机错误:组件挂载时先调用
connectWebSocket再调用startPing,此时webSocket.value可能还未初始化,startPing中的逻辑会报错,心跳机制失效,服务器因无活跃信号断开连接。
修复方案:
- 修正参数拼接逻辑:
// 在connectWebSocket中替换原URL拼接代码 const token = localStorage.getItem("keycloakToken"); const urlParams = new URLSearchParams(); urlParams.append('Authorization', `Bearer ${token}`); const completeSocketURL = socketUrl.includes('?') ? `${socketUrl}&${urlParams.toString()}` : `${socketUrl}?${urlParams.toString()}`; const socket = new WebSocket(completeSocketURL); - 避免覆盖
onclose事件:
将startPing中的事件绑定改为addEventListener,保留原有逻辑:export const startPing = () => { const pingInterval = setInterval(() => { if (webSocket.value && webSocket.value.readyState === WebSocket.OPEN) { webSocket.value.send("ping"); } }, 30000); webSocket.value.addEventListener('close', (event) => { isConnected.value = false; console.log("WS Disconnected : " + JSON.stringify(event, null, 4)); clearInterval(pingInterval); }); }; - 调整Ping启动时机:
把startPing移到connectWebSocket的onopen回调中,确保连接成功后再启动心跳:
同时删除组件socket.onopen = function (event) { isConnected.value = true; console.log("Connection Opened : " + JSON.stringify(event, null, 4)); startPing(); // 连接建立后启动心跳 };onMounted中的startPing()调用。
2. 携带Token的正确方式
WebSocket携带Token主要有两种可行方案:
- URL参数传递:即你当前采用的方式,注意参数拼接的正确性(已在上方修正),适合大多数浏览器环境。
- Cookie传递:如果前后端同域,且Token存储在Cookie中,WebSocket握手会自动携带Cookie,无需额外处理(需服务器支持从Cookie读取Token)。
注:浏览器原生WebSocket构造函数不支持直接设置请求头,若服务器要求通过请求头携带Token,可使用第三方库(如vue-native-websocket),但兼容性有限。
额外检查项
- 打开浏览器Network面板,查看WebSocket握手请求的响应状态码是否为
101 Switching Protocols,确认参数是否正确传递。 - 验证
localStorage.getItem("keycloakToken")获取的Token是否与Postman中使用的一致,且未过期。 - 查看服务器端WebSocket日志,确认断开原因(如Token验证失败、参数格式错误)。
内容的提问来源于stack exchange,提问作者BATMAN_2008
相关产品推荐
相关产品推荐

