Сшивание безопасных подписчиков с помощью makeRemoteExecutableSchema
Мы реализовали схему сшивания, когда сервер GraphQL извлекает схему с двух удаленных серверов и сшивает их вместе. Все работало нормально, когда мы работали только с Query and Mutations, но теперь у нас есть прецедент, где нам даже нужно сшивать подписки, а удаленная схема имеет на ней аутентификацию.
Нам сложно определить, как передать токен авторизации, полученный в connectionParams от клиента к удаленному серверу через шлюз.
Вот как мы изучаем схему:
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
У меня есть одно рабочее решение: идея состоит в том, чтобы не создать один экземпляр 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
.
Чтобы использовать эти клиенты в удаленной схеме, вам нужно сделать две вещи:
-
выставить их в контексте:
const server = new GraphQLServer({
schema,
context: ({ connection }) => {
if (connection && connection.context) {
return connection.context;
}
}
});
-
используйте специальную реализацию ссылок вместо 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