3

我了解在创建 GraphQL 服务器时如何设置上下文对象,例如

const app = express();
app.use(GRAPHQL_URL, graphqlExpress({
            schema,
            context: {
                foo: 'bar'
            },
    }));

以便在处理传入请求时将上下文对象传递给我的解析器。

但是,当解析器由订阅触发时,我没有看到此上下文对象(即客户端订阅了 GraphQL 订阅,并定义了订阅触发时要发送给它们的数据的形状);在这种情况下,上下文似乎是一个空对象。

有没有办法确保在 PubSub.publish() 调用之后调用解析器时正确设置我的上下文对象?

4

2 回答 2

1

我猜你正在使用这个包subscription-transport-ws。在这种情况下,可以在不同的执行步骤中添加上下文值。请参阅API。两种可能的情况

  1. 如果您有某种身份验证。您可以在onConnect执行步骤的上下文中添加查看器。这是在第一次连接到 websocket 时完成的,并且在连接关闭并再次打开之前不会改变。见例子

  2. 如果您想更动态地添加上下文,您可以在执行步骤之前添加一种中间件。它可能如下所示:

const middleware = (args) => new Promise((resolve, reject) => {
  const [schema, document, root, context, variables, operation] = args;
  context.foo = "bar"; // add something to context
  resolve(args);
})

subscriptionServer = SubscriptionServer.create({
  schema: executable.schema,
  subscribe,
  execute: (...args) => middleware(args).then(args => {
    return execute(...args);
  })
}, {
  server: websocketServer,
  path: "/graphql",
}, );

于 2017-08-09T05:50:21.547 回答
0

这是我的解决方案:

  1. 您可以传递上下文并对 graphql 订阅(WebSocket)进行身份验证,如下所示:
const server = new ApolloServer({
    typeDefs,
    resolvers,
    context: contextFunction,
    introspection: true,
    subscriptions: {
      onConnect: (
        connectionParams: IWebSocketConnectionParams,
        webSocket: WebSocket,
        connectionContext: ConnectionContext,
      ) => {
        console.log('websocket connect');
        console.log('connectionParams: ', connectionParams);
        if (connectionParams.token) {
          const token: string = validateToken(connectionParams.token);
          const userConnector = new UserConnector<IMemoryDB>(memoryDB);
          let user: IUser | undefined;
          try {
            const userType: UserType = UserType[token];
            user = userConnector.findUserByUserType(userType);
          } catch (error) {
            throw error;
          }

          const context: ISubscriptionContext = {
            // pubsub: postgresPubSub,
            pubsub,
            subscribeUser: user,
            userConnector,
            locationConnector: new LocationConnector<IMemoryDB>(memoryDB),
          };

          return context;
        }

        throw new Error('Missing auth token!');
      },
      onDisconnect: (webSocket: WebSocket, connectionContext: ConnectionContext) => {
        console.log('websocket disconnect');
      },
    },
  });

  1. 您可以使用解析器中的方法传递解析器的上下文参数,pubsub.publish如下所示:
addTemplate: (
      __,
      { templateInput },
      { templateConnector, userConnector, requestingUser }: IAppContext,
    ): Omit<ICommonResponse, 'payload'> | undefined => {
      if (userConnector.isAuthrized(requestingUser)) {
        const commonResponse: ICommonResponse = templateConnector.add(templateInput);
        if (commonResponse.payload) {
          const payload = {
            data: commonResponse.payload,
            context: {
              requestingUser,
            },
          };
          templateConnector.publish(payload);
        }

        return _.omit(commonResponse, 'payload');
      }
    },
  1. Subscription现在,我们可以在解析器subscribe方法中获取 http 请求上下文和订阅(websocket)上下文,如下所示:
Subscription: {
    templateAdded: {
      resolve: (
        payload: ISubscriptionPayload<ITemplate, Pick<IAppContext, 'requestingUser'>>,
        args: any,
        subscriptionContext: ISubscriptionContext,
        info: any,
      ): ITemplate => {
        return payload.data;
      },
      subscribe: withFilter(templateIterator, templateFilter),
    },
  },
async function templateFilter(
  payload?: ISubscriptionPayload<ITemplate, Pick<IAppContext, 'requestingUser'>>,
  args?: any,
  subscriptionContext?: ISubscriptionContext,
  info?: any,
): Promise<boolean> {
  console.count('templateFilter');
  const NOTIFY: boolean = true;
  const DONT_NOTIFY: boolean = false;
  if (!payload || !subscriptionContext) {
    return DONT_NOTIFY;
  }

  const { userConnector, locationConnector } = subscriptionContext;
  const { data: template, context } = payload;

   if (!subscriptionContext.subscribeUser || !context.requestingUser) {
    return DONT_NOTIFY;
  }

  let results: IUser[];
  try {
    results = await Promise.all([
      userConnector.findByEmail(subscriptionContext.subscribeUser.email),
      userConnector.findByEmail(context.requestingUser.email),
    ]);
  } catch (error) {
    console.error(error);
    return DONT_NOTIFY;
  }

  //...
  return true;
}

如您所见,现在我们从 HTTP request 获取订阅用户(与 graphql webserver 建立 WebSocket 连接)和 HTTP request user(将突变发送到 graphql webserver subscriptionContextcontext

如果函数的返回值是真的,那么你就可以做剩下的工作了templateFilter,然后WebSocket会推送消息给订阅用户payload.data,否则,它不会。

templateFilter函数将根据订阅用户的数量执行多次,这意味着它是可迭代的。现在您在此函数中获取每个订阅用户,并执行您的业务逻辑来决定是否将 WebSocket 消息推送到订阅用户(客户端)。

请参阅github 示例存储库

文章:

于 2019-08-20T15:46:18.993 回答