You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 00:30:56