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

Golang WebSocket客户端连接NestJS网关失败求可用示例

解决Golang客户端连接NestJS WebSocket网关失败的问题

问题现象

连接NestJS网关WebSocket服务器时,Golang客户端使用gorilla/websocket库出现错误:

2024/06/27 23:33:48 Error connecting to WebSocket: unexpected EOF

Postman可以正常连接,但Golang客户端始终无法成功建立连接。

问题原因

NestJS默认使用SocketIO作为WebSocket适配器,而gorilla/websocket是标准WebSocket协议的客户端,不兼容SocketIO的自定义握手协议。你的NestJS代码虽导入了ws库,但未配置使用WsAdapter,因此网关实际运行在SocketIO模式下,导致标准WebSocket客户端连接失败。

解决方案

1. 修改NestJS网关,启用标准WebSocket协议

需要配置WsAdapter让NestJS使用标准WebSocket协议,并修正ws库的API调用:

import {
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
  OnGatewayInit,
  OnGatewayConnection,
  OnGatewayDisconnect,
  WsAdapter,
} from '@nestjs/websockets';
import { Server, WebSocket } from 'ws';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'; // 替换为你的实际模块路径

// 应用启动时配置WsAdapter
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useWebSocketAdapter(new WsAdapter(app));
  await app.listen(3000);
}
bootstrap();

@WebSocketGateway()
export class SockServerGateway
  implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
  @WebSocketServer() server: Server;
  private clients: Map<string, WebSocket> = new Map();

  afterInit(server: Server) {
    console.log('WebSocket server initialized');
  }

  handleConnection(client: WebSocket) {
    // ws库实例默认无id属性,手动生成唯一ID
    const clientId = Math.random().toString(36).substring(2, 15);
    (client as any).id = clientId;
    this.clients.set(clientId, client);
    console.log(`Client connected: ${clientId}`);
    this.displayClients();
  }

  handleDisconnect(client: WebSocket) {
    const clientId = (client as any).id;
    this.clients.delete(clientId);
    console.log(`Client disconnected: ${clientId}`);
    this.displayClients();
  }

  @SubscribeMessage('message')
  handleMessage(client: WebSocket, payload: any): void {
    const clientId = (client as any).id;
    const receivedData = payload.data; // 解析客户端发送的JSON数据
    console.log(`Received message from ${clientId}: ${receivedData}`);
    
    // ws库使用send方法发送JSON格式消息,而非emit
    client.send(JSON.stringify({ event: 'message', data: 'xxx' }));
    
    // 广播消息给所有在线客户端
    this.server.clients.forEach((ws) => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ 
          event: 'message', 
          data: `Message from ${clientId}: ${receivedData}` 
        }));
      }
    });
  }

  private displayClients() {
    console.log('Connected clients:');
    this.clients.forEach((_, id) => {
      console.log(id);
    });
  }

  sendMessageToClient(clientId: string, message: string): void {
    const client = this.clients.get(clientId);
    if (client && client.readyState === WebSocket.OPEN) {
      client.send(JSON.stringify({ event: 'message', data: message }));
    } else {
      console.log(`Client with ID ${clientId} not found or disconnected`);
    }
  }

  sendMessageToAllClients(message: string): void {
    this.server.clients.forEach((ws) => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ event: 'message', data: message }));
      }
    });
  }
}

2. 适配的Golang客户端代码

以下是可以成功连接上述NestJS网关的Golang客户端示例:

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/signal"
	"time"

	"github.com/gorilla/websocket"
)

// 定义与NestJS匹配的消息结构体
type SocketMessage struct {
	Event string `json:"event"`
	Data  string `json:"data"`
}

func main() {
	url := "ws://localhost:3000"

	// 配置Dialer,添加Origin头避免跨域拦截
	dialer := websocket.DefaultDialer
	dialer.Subprotocols = []string{"ws"}

	conn, _, err := dialer.Dial(url, map[string][]string{
		"Origin": {"http://localhost:3000"},
	})
	if err != nil {
		log.Fatalf("连接WebSocket失败: %v", err)
	}
	defer conn.Close()

	// 启动协程处理接收消息
	go func() {
		defer conn.Close()
		for {
			_, msgBytes, err := conn.ReadMessage()
			if err != nil {
				log.Printf("读取消息失败: %v", err)
				return
			}

			var msg SocketMessage
			if err := json.Unmarshal(msgBytes, &msg); err != nil {
				log.Printf("解析消息失败: %v", err)
				continue
			}

			fmt.Printf("收到[%s]: %s\n", msg.Event, msg.Data)
		}
	}()

	// 定时发送消息
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	done := make(chan os.Signal, 1)
	signal.Notify(done, os.Interrupt)

	for {
		select {
		case <-done:
			fmt.Println("正在退出...")
			// 发送优雅关闭帧
			err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
			if err != nil {
				log.Printf("发送关闭消息失败: %v", err)
			}
			return
		case <-ticker.C:
			// 构造符合网关要求的消息格式
			sendMsg := SocketMessage{
				Event: "message",
				Data:  "Hello from Go!",
			}
			msgBytes, err := json.Marshal(sendMsg)
			if err != nil {
				log.Printf("序列化消息失败: %v", err)
				continue
			}

			err = conn.WriteMessage(websocket.TextMessage, msgBytes)
			if err != nil {
				log.Printf("发送消息失败: %v", err)
				return
			}
			fmt.Printf("发送[%s]: %s\n", sendMsg.Event, sendMsg.Data)
		}
	}
}

关键说明

  • NestJS端:必须配置WsAdapter才能支持标准WebSocket协议,同时使用ws库的send方法替代SocketIO的emit方法,消息需封装为JSON格式。
  • Golang端:需发送包含event字段的JSON消息,匹配NestJS的@SubscribeMessage监听;添加Origin头避免跨域拦截。

内容的提问来源于stack exchange,提问作者EngVivo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 19:20:53