GraphQL Subscriptions与WebSocket

GraphQL Subscriptions 是 GraphQL 规范的一个扩展,它允许客户端订阅数据更新,从而实现实时通信。与传统的 RESTful API 不同,REST API 通常是基于轮询或长轮询的模式,而 GraphQL Subscriptions 则通过 WebSocket 协议实现了双向的持久连接,从而在数据发生变化时即时通知客户端。

GraphQL Subscriptions 的原理

在 GraphQL 中,Subscriptions 类似于 Queries 和 Mutations,但是它们用于订阅数据流而不是请求单次数据。当订阅一个资源时,客户端会持续监听这个资源的变化,一旦有变化发生,服务器就会通过 WebSocket 推送更新数据给客户端。

WebSocket 在 GraphQL Subscriptions 中的作用

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它在客户端和服务器之间建立了一个持久的连接,允许双方发送数据。在 GraphQL Subscriptions 中,WebSocket 被用来维持一个长期的连接,以便服务器可以在数据更新时主动推送给客户端。

实现 GraphQL Subscriptions

下面我们将逐步介绍如何在服务器端和客户端实现 GraphQL Subscriptions。

服务器端实现

使用 Apollo Server 和 WebSocket 来设置 GraphQL Subscriptions

javascript 复制代码
// server.js
const { ApolloServer, PubSub } = require('apollo-server-express');
const express = require('express');
const http = require('http');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const { WebSocketServer } = require('ws');
const { useServer } = require('graphql-ws/lib/use/ws');

const pubsub = new PubSub();

const typeDefs = `
  type Query {
    message: String
  }

  type Subscription {
    message: String!
  }
`;

const resolvers = {
  Query: {
    message: () => 'Hello World!',
  },
  Subscription: {
    message: {
      subscribe: () => pubsub.asyncIterator(['MESSAGE_ADDED']),
    },
  },
};

const schema = makeExecutableSchema({ typeDefs, resolvers });

const app = express();
const httpServer = http.createServer(app);

const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql',
});

const serverCleanup = useServer({ schema }, wsServer);

const apolloServer = new ApolloServer({
  schema,
});

apolloServer.applyMiddleware({ app });

httpServer.listen({ port: 4000 }, () =>
  console.log(`🚀 Server ready at http://localhost:4000${apolloServer.graphqlPath}`)
);

在这个例子中,我们创建了一个简单的 GraphQL 服务器,它支持查询和订阅。我们使用 PubSub 类来管理订阅事件,当有新消息时,我们可以通过 pubsub.publish('MESSAGE_ADDED', { message: 'New Message' }) 来发布事件。

客户端实现

客户端可以使用 apollo-link-wssubscriptions-transport-ws 来连接到 GraphQL Subscriptions 服务器。

javascript 复制代码
// client.js
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { split } from '@apollo/client/link/core';
import { getMainDefinition } from '@apollo/client/utilities';
import { WebSocketLink } from '@apollo/client/link/ws';

const httpLink = createHttpLink({
  uri: 'http://localhost:4000/graphql',
});

const wsLink = new WebSocketLink({
  uri: 'ws://localhost:4000/graphql',
  options: {
    reconnect: true,
  },
});

const link = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

const client = new ApolloClient({
  link,
  cache: new InMemoryCache(),
});

client.subscribe({
  query: gql`
    subscription {
      message
    }
  `,
}).subscribe({
  next: (data) => console.log('Received data:', data),
  error: (err) => console.error('Subscription error:', err),
});

在客户端,我们创建了一个 ApolloClient 实例,并且使用 split 函数来决定何时使用 WebSocketLink 和何时使用 HTTP Link。这样,当执行订阅操作时,将使用 WebSocketLink;而执行查询或变异操作时,将使用 HTTP Link

GraphQL Subscriptions 的优点

  • 实时性:Subscriptions 可以实现实时数据更新,无需轮询。
  • 减少带宽消耗:只有在数据变化时才发送数据,减少了不必要的网络流量。
  • 更好的用户体验:用户可以立即看到数据的更新,提高了应用的响应速度和交互性。

处理多个订阅和取消订阅

在实际应用中,你可能需要处理多个订阅,以及在不需要时取消订阅。Apollo Client 和其他 GraphQL 客户端库提供了相应的机制来管理这些操作。

处理多个订阅

在客户端,你可以同时订阅多个不同的数据流。例如:

javascript 复制代码
// client.js
const subscription1 = client.subscribe({
  query: gql`
    subscription {
      message1
    }
  `,
});

const subscription2 = client.subscribe({
  query: gql`
    subscription {
      message2
    }
  `,
});

subscription1.subscribe({
  next: (data) => console.log('Received data for subscription 1:', data),
});

subscription2.subscribe({
  next: (data) => console.log('Received data for subscription 2:', data),
});

每个订阅都可以独立处理数据,并且可以有不同的回调函数。

取消订阅

当不再需要某个订阅时,你应该取消订阅,以避免不必要的数据传输和资源消耗。在 Apollo Client 中,你可以通过调用订阅返回的 unsubscribe 方法来取消订阅:

javascript 复制代码
// client.js
const subscription = client.subscribe({
  query: gql`
    subscription {
      message
    }
  `,
}).subscribe({
  next: (data) => console.log('Received data:', data),
  complete: () => console.log('Subscription completed'),
  error: (error) => console.error('Subscription error:', error),
});

// Later...
subscription.unsubscribe();

错误处理和重连机制

在使用 WebSocket 进行通信时,可能会遇到网络中断、服务器重启等问题。因此,错误处理和重连机制非常重要。

错误处理

在客户端订阅时,你可以提供 error 回调来处理订阅过程中发生的任何错误:

javascript 复制代码
// client.js
client.subscribe({
  query: gql`
    subscription {
      message
    }
  `,
}).subscribe({
  next: (data) => console.log('Received data:', data),
  error: (error) => console.error('Subscription error:', error),
});
重连机制

subscriptions-transport-wsapollo-link-ws 都提供了自动重连的功能,可以在 WebSocket 连接断开后尝试重新建立连接。你可以在创建 WebSocketLink 时设置 reconnect 选项:

javascript 复制代码
// client.js
const wsLink = new WebSocketLink({
  uri: 'ws://localhost:4000/graphql',
  options: {
    reconnect: true,
  },
});

总结

GraphQL Subscriptions 通过 WebSocket 实现了实时数据传输,为现代 Web 应用提供了强大的实时通信能力。通过上述的服务器端和客户端示例,你可以开始在自己的项目中实现 GraphQL Subscriptions,以构建实时更新的应用程序。随着 GraphQL 生态系统的不断发展,Subscriptions 将成为构建实时应用的一个重要工具。

相关推荐
counterxing1 小时前
我整理了一个免费开发资源目录,还做成了 CLI 和 MCP
前端·agent·ai编程
子兮曰7 小时前
Bun v1.3.14 深度解析:Image API、HTTP/3、全局虚拟存储与五十项变革
前端·后端·bun
kyriewen8 小时前
今天,百年巨头一次砍了9200人,而一个离职科学家的实话让全网睡不着觉
前端·openai·ai编程
问心无愧05139 小时前
ctf show web 入门42
android·前端·android studio
kyriewen9 小时前
老板逼我上AI,我偷偷在浏览器里跑LLaMA,省下20万API费
前端·react.js·llm
Beginner x_u9 小时前
前端八股整理(手写 02)|数组转树、数组扁平化、随机打乱一个数组
前端·数组·数组转树·数组扁平化
KaMeidebaby9 小时前
卡梅德生物技术快报|禽类成纤维细胞 FISH 实验:鸟类性别染色体基因定位技术实现与数据验证
前端·数据库·其他·百度·新浪微博
天若有情6739 小时前
前端高阶性能优化:跳出传统懒加载与预加载,基于用户行为做轻量预判加载
前端·性能优化
小小小小宇10 小时前
前端转后端:SQL 是什么
前端
张元清11 小时前
React Observer Hooks:7 种监听 DOM 而不写样板代码的方式
前端·javascript·面试