我在解决如何将 Relay Modern 网络层与我的websocket实例连接时遇到问题。
我目前正在将websocket实例实例化为:
const subscriptionWebSocket = new ReconnectingWebSocket('ws://url.url/ws/subscriptions/', null, options);
我正在指定订阅并创建requestSubscription
的新实例:
const subscription = graphql`
subscription mainSubscription {
testData {
anotherNode {
data
}
}
}
`;
requestSubscription(
environment,
{
subscription,
variables: {},
onComplete: () => {...},
onError: (error) => {...},
onNext: (response) => {...},
updater: (updaterStoreConfig) => {...},
},
);
然后允许我发送任何订阅请求:
function subscriptionHandler(subscriptionConfig, variables, cacheConfig, observer) {
subscriptionWebSocket.send(JSON.stringify(subscriptionConfig.text));
return {
dispose: () => {
console.log('subscriptionHandler: Disposing subscription');
},
};
}
const network = Network.create(fetchQuery, subscriptionHandler);
到我的服务器(目前正在使用Graphene-python),我能够解释服务器上收到的消息。
然而,我遇到的问题是如何回应订阅;例如,当我的数据库中的某些内容发生变化时,我想生成一个响应并返回给任何潜在订阅者。
问题是,如何将我的websocket实例中的 onMessage 事件连接到我的中继现代网络层?我已经浏览了继电器的源代码,但似乎无法弄清楚应该采用什么样的回调,或者应该采用什么方法实现onreceive。
任何提示都表示赞赏。
答案 0 :(得分:5)
我已经成功订阅了Relay Modern的作品,并希望分享我的最小设置,也许这对某人有帮助!
请注意,我没有使用WebSocket
,而是可以在subscriptions-transport-ws
中找到SubscriptionClient
来管理与服务器的连接。
这是我的最小设置代码:
import { SubscriptionClient } from 'subscriptions-transport-ws'
const {
Environment,
Network,
RecordSource,
Store,
} = require('relay-runtime')
const store = new Store(new RecordSource())
const fetchQuery = (operation, variables) => {
return fetch('https://api.graph.cool/relay/v1/__GRAPHCOOL_PROJECT_ID__', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then(response => {
return response.json()
})
}
const websocketURL = 'wss://subscriptions.graph.cool/v1/__GRAPHCOOL_PROJECT_ID__'
function setupSubscription(
config,
variables,
cacheConfig,
observer,
) {
const query = config.text
const subscriptionClient = new SubscriptionClient(websocketURL, {reconnect: true})
const id = subscriptionClient.subscribe({query, variables}, (error, result) => {
observer.onNext({data: result})
})
}
const network = Network.create(fetchQuery, setupSubscription)
const environment = new Environment({
network,
store,
})
export default environment
import {
graphql,
requestSubscription
} from 'react-relay'
import environment from '../Environment'
const newLinkSubscription = graphql`
subscription NewLinkSubscription {
Link {
mutation
node {
id
description
url
createdAt
postedBy {
id
name
}
}
}
}
`
export default (onNext, onError, onCompleted, updater) => {
const subscriptionConfig = {
subscription: newLinkSubscription,
variables: {},
onError,
onNext,
onCompleted,
updater
}
requestSubscription(
environment,
subscriptionConfig
)
}
现在您只需使用导出的函数进行订阅即可。例如,在我componentDidMount
中的一个React组件中,我现在可以执行以下操作:
componentDidMount() {
NewLinkSubscription(
response => console.log(`Received data: `, response),
error => console.log(`An error occurred:`, error),
() => console.log(`Completed`)
)
}
请注意,SubscriptionClient
只能在您的服务器实施this协议时使用!
如果您想了解更多信息,请查看详细描述如何使订阅与Relay Modern一起使用的fullstack How to GraphQL tutorial。
答案 1 :(得分:3)
我将在此主题中找到帮助后写下我是如何处理此问题的。它可能适用于其他人。这非常依赖于您选择的服务器端解决方案。
首先,我构建了一个SubscriptionHandler,它将通过SubscriptionHandler#setupSubscription处理requestStream#subscribeFunction。
SubscriptionHandler实例化WebSocket(使用自定义版本的ReconnectingWebSockets)并将onmessage事件附加到内部方法(SubscriptionHandler#receiveSubscriptionPayload),该方法将有效负载添加到相应的请求中。
我们通过SubscriptionHandler#newSubscription创建新的订阅,它将使用内部属性SubscriptionHandler.subscriptions来添加此订阅的键控条目(我们在查询和变量上使用MD5-hash util);意味着该对象将出现:
SubscriptionHandler.subscriptions = {
[md5hash]: {
query: QueryObject,
variables: SubscriptionVariables,
observer: Observer (contains OnNext method)
}
每当服务器发送订阅响应时,将调用SubscriptionHandler#receiveSubscriptionPayload方法,它将使用查询/变量md5哈希识别有效负载所属的订阅,然后使用SubscriptionHandler.subscriptions观察者onNext方法。
此方法要求服务器返回如下消息:
export type ServerResponseMessageParsed = {
payload: QueryPayload,
request: {
query: string,
variables: Object,
}
}
我不知道这是否是处理订阅的好方法,但它现在适用于我当前的设置。
SubscriptionHandler.js
class SubscriptionHandler {
subscriptions: Object;
subscriptionEnvironment: RelayModernEnvironment;
websocket: Object;
/**
* The SubscriptionHandler constructor. Will setup a new websocket and bind
* it to internal method to handle receving messages from the ws server.
*
* @param {string} websocketUrl - The WebSocket URL to listen to.
* @param {Object} webSocketSettings - The options object.
* See ReconnectingWebSocket.
*/
constructor(websocketUrl: string, webSocketSettings: WebSocketSettings) {
// All subscription hashes and objects will be stored in the
// this.subscriptions attribute on the subscription handler.
this.subscriptions = {};
// Store the given environment internally to be reused when registering new
// subscriptions. This is required as per the requestRelaySubscription spec
// (method requestSubscription).
this.subscriptionEnvironment = null;
// Create a new WebSocket instance to be able to receive messages on the
// given URL. Always opt for default protocol for the RWS, second arg.
this.websocket = new ReconnectingWebSocket(
websocketUrl,
null, // Protocol.
webSocketSettings,
);
// Bind an internal method to handle incoming messages from the websocket.
this.websocket.onmessage = this.receiveSubscriptionPayload;
}
/**
* Method to attach the Relay Environment to the subscription handler.
* This is required as the Network needs to be instantiated with the
* SubscriptionHandler's methods, and the Environment needs the Network Layer.
*
* @param {Object} environment - The apps environment.
*/
attachEnvironment = (environment: RelayModernEnvironment) => {
this.subscriptionEnvironment = environment;
}
/**
* Generates a hash from a given query and variable pair. The method
* used is a recreatable MD5 hash, which is used as a 'key' for the given
* subscription. Using the MD5 hash we can identify what subscription is valid
* based on the query/variable given from the server.
*
* @param {string} query - A string representation of the subscription.
* @param {Object} variables - An object containing all variables used
* in the query.
* @return {string} The MD5 hash of the query and variables.
*/
getHash = (query: string, variables: HashVariables) => {
const queryString = query.replace(/\s+/gm, '');
const variablesString = JSON.stringify(variables);
const hash = md5(queryString + variablesString).toString();
return hash;
}
/**
* Method to be bound to the class websocket instance. The method will be
* called each time the WebSocket receives a message on the subscribed URL
* (see this.websocket options).
*
* @param {string} message - The message received from the websocket.
*/
receiveSubscriptionPayload = (message: ServerResponseMessage) => {
const response: ServerResponseMessageParsed = JSON.parse(message.data);
const { query, variables } = response.request;
const hash = this.getHash(query, variables);
// Fetch the subscription instance from the subscription handlers stored
// subscriptions.
const subscription = this.subscriptions[hash];
if (subscription) {
// Execute the onNext method with the received payload after validating
// that the received hash is currently stored. If a diff occurs, meaning
// no hash is stored for the received response, ignore the execution.
subscription.observer.onNext(response.payload);
} else {
console.warn(Received payload for unregistered hash: ${hash});
}
}
/**
* Method to generate new subscriptions that will be bound to the
* SubscriptionHandler's environment and will be stored internally in the
* instantiated handler object.
*
* @param {string} subscriptionQuery - The query to subscribe to. Needs to
* be a validated subscription type.
* @param {Object} variables - The variables for the passed query.
* @param {Object} configs - A subscription configuration. If
* override is required.
*/
newSubscription = (
subscriptionQuery: GraphQLTaggedNode,
variables: Variables,
configs: GraphQLSubscriptionConfig,
) => {
const config = configs || DEFAULT_CONFIG;
requestSubscription(
this.subscriptionEnvironment,
{
subscription: subscriptionQuery,
variables: {},
...config,
},
);
}
setupSubscription = (
config: ConcreteBatch,
variables: Variables,
cacheConfig: ?CacheConfig,
observer: Observer,
) => {
const query = config.text;
// Get the hash from the given subscriptionQuery and variables. Used to
// identify this specific subscription.
const hash = this.getHash(query, variables);
// Store the newly created subscription request internally to be re-used
// upon message receival or local data updates.
this.subscriptions[hash] = { query, variables };
const subscription = this.subscriptions[hash];
subscription.observer = observer;
// Temp fix to avoid WS Connection state.
setTimeout(() => {
this.websocket.send(JSON.stringify({ query, variables }));
}, 100);
}
}
const subscriptionHandler = new SubscriptionHandler(WS_URL, WS_OPTIONS);
export default subscriptionHandler;
答案 2 :(得分:1)
我认为this repo符合您的需求。 帮助您创建订阅服务器端
答案 3 :(得分:1)
对于最近遇到这种情况的人来说,由于最近涉及的库更新,我在上述解决方案中都没有成功。然而,他们是一个伟大的起源来源,我把一个基于官方接力现代todo例子的小例子放在一起,它是非常简约的,并使用来自Apollo的助手库,但与现代接力很好地合作:
https://github.com/jeremy-colin/relay-examples-subscription
它包括服务器和客户端
希望它可以提供帮助