keystone.js graphql 订阅不起作用 - 代码:4406,原因:“子协议不可接受”

问题描述 投票:0回答:1

我正在开发一个 keystone 应用程序,我们想在其中使用 graphql 订阅。

遵循文档: https://keystonejs.com/docs/config/config#extend-http-server

以及 websocket 文件的 keytone github 示例: https://github.com/keystonejs/keystone/blob/main/examples/extend-graphql-subscriptions/websocket.ts

创建的订阅出现在 Apollo Playground 架构中。

只要我从代码中注释掉 graphql-ws,websocket 连接就可以工作。当我添加来自 graphql-ws 的 wsUseServer 时,它在这两次尝试中给出以下错误:

  • wscat -c ws://localhost:3000/api/graphql >
    已连接(按 CTRL+C 退出) 已断开连接(代码:4406,原因:“子协议不可接受”)
  • wscat -c ws://localhost:3000/api/graphql -s graphql-ws 错误:服务器未发送子协议

我的 websocket.ts 代码与示例相同,除了用于调试的日志和注释测试行:


import type http from 'http'
import { useServer as wsUseServer } from 'graphql-ws/lib/use/ws'
import { WebSocketServer } from 'ws'
import { PubSub } from 'graphql-subscriptions'
import { parse } from 'graphql'
import { type Context } from '.keystone/types'

// Setup pubsub as a Global variable in dev so it survives Hot Reloads.
declare global {
  let graphqlSubscriptionPubSub: PubSub
}

// The 'graphql-subscriptions' pubsub library is not recommended for production use, but can be useful as an example
//   for details see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#the-pubsub-class
export const pubSub = global.graphqlSubscriptionPubSub || new PubSub()
globalThis.graphqlSubscriptionPubSub = pubSub

export function extendHttpServer (
  httpServer: http.Server,
  commonContext: Context,
) {
  console.log('HTTP Server:', httpServer);  
  console.log(commonContext.graphql.schema)

  // Setup WebSocket server using 'ws'
  const wss = new WebSocketServer({
    server: httpServer,
    path: '/api/graphql',
    /*handleProtocols: (protocols) => {
      return Array.from(protocols).includes('graphql-ws') ? 'graphql-ws' : null;
    }*/
  })

  // Setup the WebSocket to handle GraphQL subscriptions using 'graphql-ws'
  wsUseServer(
    {
      schema: commonContext.graphql.schema,
      // run these onSubscribe functions as needed or remove them if you don't need them
      onSubscribe: async (ctx: any, msg) => {
        const context = await commonContext.withRequest(ctx.extra.request)
        // Return the execution args for this subscription passing through the Keystone Context
        return {
          schema: commonContext.graphql.schema,
          operationName: msg.payload.operationName,
          document: parse(msg.payload.query),
          variableValues: msg.payload.variables,
          contextValue: context,
        }
      },
    },
    wss
  )

  //wsUseServer({ schema: commonContext.graphql.schema }, wss); // For testing
  //const ws = new WebSocket('ws://localhost:3000/api/graphql', 'graphql-ws'); // For testing

  // Optional: Log connection events
  wss.on('connection', (ws, request) => {
    console.log('New WebSocket connection');
    console.log('Requested Protocols:', request.headers['sec-websocket-protocol']);
    console.log('Selected Protocol:', ws.protocol); // Log the subprotocol selected by the server    

    ws.on('close', (error) => {
      console.error('WebSocket error:', error);
    });    
    
    ws.on('message', (message) => {
      console.log('Received message:', message);
    });

    ws.on('error', (error) => {
      console.error('WebSocket error:', error);
    });
    ws.onerror = (evt) => {
  console.log('Message received:', evt);
};
  });

  // Send the time every second as an interval example of pub/sub
  /*setInterval(() => {
    console.log('TIME', Date.now())
    pubSub.publish('TIME', {
      time: {
        iso: new Date().toISOString(),
      },
    })
  }, 1000)*/
}

我删除了 wsUseServer (Graphql 订阅)并保持连接。但我想要的是使用订阅。 SetInterval(上面注释掉)在连接时起作用。

我将 [handleProtocols] 添加到 websocketserver(上面已注释掉)。没解决。

在开发和生产环境中测试了两个 wscat 条目,两者的行为是相同的。

我期望能够维持与 graphql-ws wsUseServer 的 websocket 连接,以便能够激活订阅。

谢谢。

websocket graphql keystonejs graphql-subscriptions keystonejs6
1个回答
0
投票

找到了解决办法。 首先,wscat 不是测试此功能的正确工具。 其次,正确的协议不是 graphql-ws,而是 graphql-transport-ws。无需指定,因为它是自动处理的。

我在 Apollo Playground 上测试了订阅,他们显示正在监听,但没有响应。

问题在于 Pubsub 的实施方式。我在每个必须使用它的文件中调用它,这会产生冲突。它必须在单个文件中启动,然后导出到想要使用它的每个文件。

© www.soinside.com 2019 - 2024. All rights reserved.