Apollo Websocket更新Context时反复重连问题求助
Apollo Websocket反复连接问题(GQLgen后端 + Apollo前端)
问题现象
前端用Apollo管理GraphQL查询与订阅,后端基于GQLgen实现Resolver。发现异常:当后端webSocketInit函数返回新创建的context对象时,前端Websocket会不断重复连接;若直接返回原始ctx,所有功能(包括订阅)均正常运行。
后端代码(GQLgen)
Websocket配置(main.go)
srv.AddTransport(&transport.Websocket{ Upgrader: websocket.Upgrader{ ReadBufferSize: 2048, WriteBufferSize: 2048, // 跨域处理 CheckOrigin: func(r *http.Request) bool { return true }, }, InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) { return webSocketInit(ctx, initPayload) }, KeepAlivePingInterval: 10 * time.Second, })
webSocketInit函数
func webSocketInit(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) { // 处理认证token ctxNew := context.WithValue(ctx, "token", token) return ctxNew, nil }
前端代码(Apollo Client)
订阅逻辑(client.tsx)
useEffect(() => { const unsubscribe = subscribeToMore({ document: MY_SUBSCRIPTION_GQL, updateQuery: (prev, { subscriptionData }: any) => { // 处理新数据 return Object.assign({}, prev, { data: newData, }); }, onError: (e) => { console.log(e); }, }); return () => unsubscribe(); }, []);
Apollo Client配置
const authLink = setContext((_, { headers }) => { const token = store.getState().user.token; return { headers: { ...headers, authorization: token ? `Bearer ${token}` : '', }, }; }); const wsLink = typeof window !== 'undefined' ? new GraphQLWsLink( createClient({ url: 'ws://localhost:8080', lazy: true, connectionParams: () => ({ Authorization: store.getState().user.token, }), on: { connected: () => { console.log('connected'); }, error: (e) => { console.log(e); }, }, }) ) : null; // HTTP 请求链路 const httpLink = new HttpLink({ uri: `http://localhost:8080/`, }); const link = typeof window !== 'undefined' && wsLink != null ? split( ({ query }) => { const def = getMainDefinition(query); return def.kind === 'OperationDefinition' && def.operation === 'subscription'; }, wsLink, authLink.concat(httpLink) ) : authLink.concat(httpLink); export const client = new ApolloClient({ link: link, cache: new InMemoryCache({ addTypename: false, }), });
问题分析与解决思路
核心排查点
- 隐性错误导致连接失败:
webSocketInit中可能存在未捕获的错误(比如token解析失败、类型断言错误),即使代码未显式抛出,隐性错误会让InitFunc返回异常,导致后端拒绝连接,触发前端反复重连。 - 上下文传递完整性:
context.WithValue本身会继承父上下文值,但GQLgen的Websocket Transport可能依赖原始ctx中的内部绑定状态(如心跳处理器、会话标识),若token处理逻辑无意中丢失这些状态,会导致连接无法维持。
具体解决步骤
- 添加错误日志与参数校验:在
webSocketInit中加入日志和校验,确保前端传递的参数合法:import ( "log" "fmt" "strings" ) func webSocketInit(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) { authToken, ok := initPayload["Authorization"].(string) if !ok || authToken == "" { log.Printf("Invalid auth payload received: %v", initPayload) return ctx, fmt.Errorf("missing or invalid authorization token") } // 统一处理token格式(去掉Bearer前缀) token := strings.TrimPrefix(authToken, "Bearer ") ctxNew := context.WithValue(ctx, "token", token) return ctxNew, nil } - 对齐前后端参数格式:确认前端
connectionParams返回的Authorization字段格式和后端预期一致(比如是否带Bearer前缀)。 - 测试Lazy模式:暂时将前端
lazy: true改为lazy: false,排除懒加载导致的连接时序问题。 - 验证心跳机制:后端设置了10秒Ping间隔,检查前端是否能正常响应Pong,未正确处理心跳也会触发重连。
内容的提问来源于stack exchange,提问作者Nicole Staline
相关产品推荐
相关产品推荐

