Web 与 HTTP 服务
你当前正在浏览历史版本 Vert.x 4.1.8 的文档。 访问本页面的 最新版本。

Vert.x Web GraphQL 服务端组件

Vert.x Web GraphQL 将 Vert.x Web 与知名的 GraphQL-Java 框架深度整合,赋予你构建高性能反应式 GraphQL 服务端的能力。

本手册为 Vert.x Web GraphQL 的参考指南。建议提前阅读并熟悉 GraphQL-Java 入门文档。

快速上手

在项目构建配置中引入依赖:

  • Maven(在 pom.xml 中):

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web-graphql</artifactId>
  <version>5.0.12</version>
</dependency>
  • Gradle(在 build.gradle 文件中):

compile 'io.vertx:vertx-web-graphql:5.0.12'

处理器装配与配置

HTTP 传输通道

创建 Vert.x Web Route 并挂载 GraphQLHandler:

GraphQL graphQL = setupGraphQLJava();

router.route("/graphql").handler(GraphQLHandler.create(graphQL));

GraphQLHandler 原生支持 Apollo APQ 自动持久化查询,只需在 GraphQL-Java 构建时完成对应配置:

graphQLBuilder.preparsedDocumentProvider(new ApolloPersistedQuerySupport(queryCache));

该 Handler 默认同时响应 GET 与 POST 请求。你也可以将其限制为特定的 HTTP 方法:

GraphQL graphQL = setupGraphQLJava();

router.post("/graphql").handler(GraphQLHandler.create(graphQL));
GraphQLHandler 必须依赖 BodyHandler 才能正常读取 POST 请求体中的 GraphQL 查询报文。

批量查询请求 (Query batching)

查询批处理(Query Batching)允许客户端向 GraphQL 端点一次性发送一个包含多个查询的 JSON 数组,而非单个请求对象。

Vert.x Web GraphQL 完整支持该特性,默认处于关闭状态。若需开启,请配置 GraphQLHandlerOptions:

GraphQLHandlerOptions options = new GraphQLHandlerOptions()
  .setRequestBatchingEnabled(true);

GraphQLHandler handler = GraphQLHandler.create(graphQL, options);

基于 WebSocket 的 GraphQL 长连接传输

Vert.x Web GraphQL 完全兼容官方标准 GraphQL over WebSocket 协议。

WebSocket 传输通道在需要使用 GraphQL Subscription(实时订阅推送)时尤为关键,同时也可用于承载普通的 Query 与 Mutation。

默认配置未锁定 Origin 标头。为防范跨站 WebSocket 劫持攻击(CSWSH),强烈建议配置 Origin 白名单属性严格校验来源。

挂载 WebSocket 处理器:

router.route("/graphql").handler(GraphQLWSHandler.create(graphQL));

客户端在建立长连接握手时会请求 graphql-transport-ws 子协议。因此必须在 HTTP 服务器选项中显式添加该子协议支持:

HttpServerOptions httpServerOptions = new HttpServerOptions()
  .addWebSocketSubProtocol("graphql-transport-ws");

若需要在同一个 URI(如 /graphql)下同时兼容 HTTP 与 WebSocket,必须确保在路由链中将 GraphQLWSHandler 挂载在 GraphQLHandler 之前:

router.route("/graphql")
  .handler(GraphQLWSHandler.create(graphQL))
  .handler(GraphQLHandler.create(graphQL));
订阅(Subscription)对应的 DataFetcher 必须返回 org.reactivestreams.Publisher 反应式流发布者实例。

内置 GraphiQL 可视化调试控制台

在开发与联调期间,在浏览器中通过可视化 IDE GraphiQL 直接编写并执行查询非常高效。

通过挂载 GraphiQLHandler 即可开箱启用:

GraphiQLHandlerOptions options = new GraphiQLHandlerOptions()
  .setEnabled(true);

GraphiQLHandler handler = GraphiQLHandler.create(vertx, options);

router.route("/graphiql*").subRouter(handler.router());

随后在浏览器中打开 http://localhost:8080/graphiql/ 即可直接交互。

出于生产安全考量,GraphiQL 界面默认处于禁用状态。若要在生产环境使用需显式调用 setEnabled(true);在开发模式(VERTXWEB_ENVIRONMENT=dev)下系统会自动激活该工具。

数据获取器 (Data Fetching)

GraphQL-Java 的 API 天然契合反应式非阻塞模型:针对 Query 默认采用异步并行执行策略(针对 Mutation 采用异步串行策略)。

为了遵守 Vert.x 的黄金铁律(绝对不阻塞 EventLoop 线程),在编写 DataFetcher 时,只需返回 Java 的 CompletionStage 或 Vert.x 的 Future,而绝不要阻塞等待:

DataFetcher<CompletionStage<List<Link>>> dataFetcher = environment -> {
  Future<List<Link>> future = retrieveLinksFromBackend(environment);
  return future.toCompletionStage();
};

RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
  .type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
  .build();

为了避免在每个 DataFetcher 中手动将 Vert.x Future 转为 CompletionStage,可以直接在 GraphQL-Java 中注册 VertxFutureAdapter 扩展插桩:

graphQLBuilder.instrumentation(VertxFutureAdapter.create());

配置后即可在 DataFetcher 中直接返回 Vert.x 的 Future 原生对象:

DataFetcher<Future<List<Link>>> dataFetcher = environment -> {
  return retrieveLinksFromBackend(environment);
};

在 DataFetcher 中获取当前请求上下文

在很多业务中,GraphQL 路由前面通常挂载了身份认证 Handler。DataFetcher 需要获知当前登录的用户是谁以收敛数据查询范围。

你可以直接从 DataFetchingEnvironment 的 GraphQL 上下文中提取 RoutingContext:

DataFetcher<CompletionStage<List<Link>>> dataFetcher = environment -> {

  RoutingContext routingContext = environment.getGraphQlContext().get(RoutingContext.class);

  UserContext user = routingContext.userContext();

  Future<List<Link>> future = retrieveLinksPostedBy(user);
  return future.toCompletionStage();
};

JSON 数据类型无缝适配

在 Vert.x 应用中,极高频地直接使用 JsonObject 与 JsonArray 表达数据。为了让 GraphQL-Java 能够原生透传读取 JsonObject 的属性字段,只需注册 JsonObjectAdapter 插桩即可:

graphQLBuilder.instrumentation(new JsonObjectAdapter());

批量加载器 (Batch loading / DataLoader)

DataLoader 能够通过批处理批量合并请求与缓存结果,彻底解决 GraphQL 著名的 N+1 查询性能问题。

首先创建批量加载器:

BatchLoaderWithContext<String, Link> linksBatchLoader = (ids, env) -> {
  // 批量根据传入的多个 ID 一次性从数据库或底层服务返回一组 Links
  return retrieveLinksFromBackend(ids, env);
};

随后在 beforeExecute 回调中为每次请求注册 DataLoader 即可:

GraphQLHandler handler = GraphQLHandler.builder(graphQL).beforeExecute(builderWithContext -> {

  DataLoader<String, Link> linkDataLoader = DataLoaderFactory.newDataLoader(linksBatchLoader);

  DataLoaderRegistry dataLoaderRegistry = new DataLoaderRegistry().register("link", linkDataLoader);

  builderWithContext.builder().dataLoaderRegistry(dataLoaderRegistry);

}).build();

GraphQL Multipart 文件流式上传

遵循标准的 GraphQL Multipart Request 开放规范。开启后客户端可以在单个 GraphQL Mutation 变更中直接完成文件二进制上传。

只需在 options 中配置 setRequestMultipartEnabled(true) 并在路由上挂载 BodyHandler 即可:

GraphQLHandlerOptions options = new GraphQLHandlerOptions()
  .setRequestMultipartEnabled(true);

GraphQLHandler graphQLHandler = GraphQLHandler.create(graphQL, options);

Router router = Router.router(vertx);

router.route().handler(BodyHandler.create());
router.route("/graphql").handler(graphQLHandler);

在 DataFetcher 内部通过 environment.getArgument("myFile") 获取 FileUpload 实例。