Сшивание защищенных подписок с использованием makeRemoteExecutableSchema - PullRequest
0 голосов
/ 25 июня 2018

Мы реализовали сшивание схем, когда сервер GraphQL выбирает схемы с двух удаленных серверов и объединяет их вместе. Все работало нормально, когда мы работали только с Query and Mutations, но теперь у нас есть сценарий использования, в котором нам даже нужно сшить подписки, и над ним реализована аутентификация удаленной схемы.

Нам трудно понять, как передать токен авторизации, полученный в соединении с клиентом, на удаленный сервер через шлюз.

Вот как мы анализируем схему:

Код шлюза API:

const getLink = async(): Promise<ApolloLink> => {
const http = new HttpLink({uri: process.env.GRAPHQL_ENDPOINT, fetch:fetch})

const link = setContext((request, previousContext) => {
    if (previousContext
        && previousContext.graphqlContext
        && previousContext.graphqlContext.request
        && previousContext.graphqlContext.request.headers
        && previousContext.graphqlContext.request.headers.authorization) {
        const authorization = previousContext.graphqlContext.request.headers.authorization;
        return {
            headers: {
                authorization
            }
        }
    }
    else {
        return {};
    }
}).concat(http);

const wsLink: any = new WebSocketLink(new SubscriptionClient(process.env.REMOTE_GRAPHQL_WS_ENDPOINT, {
    reconnect: true,
    // There is no way to update connectionParams dynamically without resetting connection
    // connectionParams: () => { 
    //     return { Authorization: wsAuthorization }
    // }
}, ws));


// Following does not work
const wsLinkContext = setContext((request, previousContext) => {
    let authToken = previousContext.graphqlContext.connection && previousContext.graphqlContext.connection.context ? previousContext.graphqlContext.connection.context.Authorization : null
    return {
        context: {
            Authorization: authToken
        }
    }
}).concat(<any>wsLink);

const url = split(({query}) => {
    const {kind, operation} = <any>getMainDefinition(<any>query);
    return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLinkContext,
link)

return url;
}

const getSchema = async (): Promise < GraphQLSchema > => {
  const link = await getLink();
  return makeRemoteExecutableSchema({
    schema: await introspectSchema(link),
    link,
  });
}
const linkSchema = `
  extend type UserPayload {
    user: User
  }
`;
const schema: any = mergeSchemas({
  schemas: [linkSchema, getSchema],
});
const server = new GraphQLServer({
  schema: schema,
  context: req => ({
    ...req,
  })
});

Есть ли способ добиться этого с помощью graphql-tools? Любая помощь приветствуется.

1 Ответ

0 голосов
/ 17 сентября 2018

У меня есть одно рабочее решение: идея состоит в том, чтобы не создавать один экземпляр SubscriptionClient для всего приложения. Вместо этого я создаю клиентов для каждого соединения с прокси-сервером:

server.start({
    port: 4000,
    subscriptions: {
      onConnect: (connectionParams, websocket, context) => {
        return {
          subscriptionClients: {
            messageService: new SubscriptionClient(process.env.MESSAGE_SERVICE_SUBSCRIPTION_URL, {
              connectionParams,
              reconnect: true,
            }, ws)
          }
        };
      },
      onDisconnect: async (websocket, context) => {
        const params = await context.initPromise;
        const { subscriptionClients } = params;
        for (const key in subscriptionClients) {
          subscriptionClients[key].close();
        }
      }
    }
  }, (options) => console.log('Server is running on http://localhost:4000'))

если у вас будет больше удаленных схем, вы просто создадите больше экземпляров SubscriptionClient на карте subscriptionClients.

Чтобы использовать этих клиентов в удаленной схеме, вам нужно сделать две вещи:

  1. раскрыть их в контексте:

    const server = new GraphQLServer({
      schema,
      context: ({ connection }) => {
        if (connection && connection.context) {
          return connection.context;
        }
      }
    });
    
  2. использовать реализацию пользовательских ссылок вместо WsLink

    (operation, forward) => {
        const context = operation.getContext();
        const { graphqlContext: { subscriptionClients } } = context;
        return subscriptionClients && subscriptionClients[clientName] && subscriptionClients[clientName].request(operation);
    };
    

Таким образом, все параметры соединения будут переданы на удаленный сервер.

Весь пример можно найти здесь: https://gist.github.com/josephktcheung/cd1b65b321736a520ae9d822ae5a951b

Отказ от ответственности:

Код не мой, так как @josephktcheung опередил меня, предоставив пример. Я просто немного помог с этим. Вот оригинальное обсуждение: https://github.com/apollographql/graphql-tools/issues/864

...