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

Meteor集成Apollo Subscription遇WebSocket连接关闭问题求助

解决Meteor中Apollo Subscription的WebSocket连接问题

我看到你在Meteor 1.6.1.1里配置Apollo Subscription时遇到了WebSocket握手失败的问题,这其实是因为Meteor自带的SockJS服务和Apollo的SubscriptionServer路径冲突导致的。下面我来一步步帮你修复这个问题,让订阅功能正常工作。

问题根源

Meteor默认会在根路径下启动SockJS服务(也就是你错误里看到的/sockjs/...路径),而如果你的Apollo SubscriptionServer直接绑定到Meteor的WebApp.httpServer上,两者会在WebSocket升级请求上产生冲突,导致握手失败。虽然你尝试了noServer: true解决了错误,但没配置客户端的WebSocket链接,所以订阅还是无法工作。

解决方案步骤

1. 修复服务器端SubscriptionServer配置

我们需要让SubscriptionServer不直接绑定到Meteor的HTTP服务器,而是通过Meteor的connectHandlers来处理/subscriptions路径的WebSocket升级请求,这样就能避开和Meteor自带SockJS的冲突。修改你的服务器代码:

import { WebApp } from 'meteor/webapp';
import { execute, subscribe } from 'graphql';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { schema } from './your-schema-path'; // 替换成你的schema实际路径

// 先创建Apollo Server,添加上下文配置用于用户验证
createApolloServer({
  schema,
  tracing: true,
  cacheControl: true,
  context: async ({ req, connection }) => {
    let loginToken;
    // HTTP请求从headers提取登录令牌
    if (req) {
      loginToken = req.headers['meteor-login-token'];
    } 
    // WS订阅从connectionParams提取登录令牌
    else if (connection) {
      loginToken = connection.context['meteor-login-token'];
    }

    // 验证令牌并获取用户(使用Meteor原生Accounts方法)
    const user = loginToken ? await Meteor.users.findOne({ 'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(loginToken) }) : null;

    return { user };
  }
});

// 创建SubscriptionServer,设置noServer: true避免直接绑定服务器
const subscriptionServer = new SubscriptionServer({
  schema,
  execute,
  subscribe,
  // 处理WS连接时的上下文,传递客户端发来的令牌
  onConnect: (connectionParams) => {
    return {
      'meteor-login-token': connectionParams['meteor-login-token']
    };
  }
}, {
  noServer: true
});

// 让Meteor的connectHandlers接管/subscriptions路径的WebSocket升级请求
WebApp.connectHandlers.use('/subscriptions', (req, res) => {
  // 只处理WebSocket升级请求
  if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') {
    res.writeHead(400);
    res.end('Expected WebSocket upgrade');
    return;
  }

  // 将请求转发给subscriptionServer处理
  subscriptionServer.handleUpgrade(req, req.socket, Buffer.alloc(0), (ws) => {
    subscriptionServer.onConnect(req, ws);
  });
});

2. 修复客户端ApolloClient配置

你的客户端目前只配置了HTTP链接,订阅需要使用WebSocket链接,所以要添加WebSocketLink,并用split来区分请求类型:查询/突变走HTTP,订阅走WS。同时要把Meteor的登录令牌通过connectionParams传给服务器:

import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, split } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { Accounts } from 'meteor/accounts-base';

// HTTP链接,保留原有的auth配置
const httpLink = createHttpLink({
  uri: '/graphql' // 确保和Apollo Server的默认路径一致
});

const authLink = new ApolloLink((operation, forward) => {
  operation.setContext(() => ({
    headers: {
      'meteor-login-token': Accounts._storedLoginToken(),
    },
  }));
  return forward(operation);
});

// WebSocket链接,用于订阅请求
const wsLink = new WebSocketLink({
  uri: `ws://localhost:3000/subscriptions`, // 和服务器端配置的路径完全匹配
  options: {
    reconnect: true,
    // 传递Meteor登录令牌到服务器
    connectionParams: {
      'meteor-login-token': Accounts._storedLoginToken()
    }
  }
});

// 拆分链接:根据请求类型自动选择HTTP或WS
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  authLink.concat(httpLink)
);

// 创建最终的ApolloClient实例
export default new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache(),
});

3. 关键验证要点

  • 确保服务器端的/subscriptions路径和客户端WebSocket链接的路径完全一致
  • 不管是HTTP请求还是WS订阅,都要正确传递meteor-login-token,这样服务器上下文才能拿到用户信息
  • 你的依赖包版本在Meteor 1.6.1.1上是兼容的,如果后续仍有问题,可以尝试同步升级subscriptions-transport-ws和apollo-link-ws的版本

测试订阅

现在你可以尝试创建一个简单的订阅查询来验证,比如:

subscription {
  sampleSubscription {
    id
    content
  }
}

在客户端用client.subscribe()或者对应的React Hooks(如useSubscription)发起订阅,应该就能正常接收服务器推送的更新了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:18:58