我们已经实现了模式拼接,其中GraphQL服务器从两个远程服务器获取模式并将它们拼接在一起。当我们只使用查询和突变时,一切工作都很好,但是现在有了一个用例,我们甚至需要缝合订阅,并且远程模式已对其进行身份验证。
我们很难确定如何将在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
来实现?任何帮助表示赞赏。
答案 0 :(得分:0)
我有一个可行的解决方案:想法是不为整个应用程序创建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
答案 1 :(得分:0)
这是一个远程模式的工作示例,其中通过webscoket进行预订,并通过http查询和更改。可以通过自定义标头(参数)保护它,并在此示例中显示。
流量
客户要求
-> context
是通过读取req
或connection
创建的(jwt被解码并在上下文中创建用户对象)
->执行远程模式
-> link
被调用
-> link
按操作划分(wsLink
用于订阅,httpLink
用于查询和变异)
-> wsLink或httpLink访问上面创建的context
(= graphqlContext)
-> wsLink或httpLink使用context
为远程模式创建标头(在本示例中为带有签名的jwt的授权标头)。
->“订阅”或“查询或变异”被转发到远程服务器。
注意
concat
。connection
,而不仅是req
。如果请求是websocket,则前者可用,它包含用户发送的元信息,例如auth令牌。node-fetch
(尤其是与打字稿不兼容)。而是使用cross-fetch
。 const wsLink = new ApolloLink(operation => {
// This is your context!
const context = operation.getContext().graphqlContext
// Create a new websocket link per request
return new WebSocketLink({
uri: "<YOUR_URI>",
options: {
reconnect: true,
connectionParams: { // give custom params to your websocket backend (e.g. to handle auth)
headers: {
authorization: jwt.sign(context.user, process.env.SUPER_SECRET),
foo: 'bar'
}
},
},
webSocketImpl: ws,
}).request(operation)
// Instead of using `forward()` of Apollo link, we directly use websocketLink's request method
})
const httpLink = setContext((_graphqlRequest, { graphqlContext }) => {
return {
headers: {
authorization: jwt.sign(graphqlContext.user, process.env.SUPER_SECRET),
},
}
}).concat(new HttpLink({
uri,
fetch,
}))
const link = split(
operation => {
const definition = getMainDefinition(operation.query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink, // <-- Executed if above function returns true
httpLink, // <-- Executed if above function returns false
)
const schema = await introspectSchema(link)
const executableSchema = makeRemoteExecutableSchema({
schema,
link,
})
const server = new ApolloServer({
schema: mergeSchemas([ executableSchema, /* ...anotherschemas */]),
context: ({ req, connection }) => {
let authorization;
if (req) { // when query or mutation is requested by http
authorization = req.headers.authorization
} else if (connection) { // when subscription is requested by websocket
authorization = connection.context.authorization
}
const token = authorization.replace('Bearer ', '')
return {
user: getUserFromToken(token),
}
},
})