Vert.x Web GraphQL

Vert.x Web GraphQL 扩展了 Vert.x Web,集成了 GraphQL-Java 库,以便您能够构建 GraphQL 服务器。

这是 Vert.x Web GraphQL 的参考文档。强烈建议您首先熟悉 GraphQL-Java API。您可以先阅读 GraphQL-Java 文档

快速入门

要使用此模块,请将以下内容添加到您的 Maven POM 文件的 依赖项 部分

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web-graphql</artifactId>
  <version>5.0.1</version>
</dependency>

或者,如果您使用 Gradle

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

处理器设置

HTTP

为此创建一个 Vert.x Web Route 和一个 GraphQLHandler

GraphQL graphQL = setupGraphQLJava();

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

GraphQLHandler 支持 Apollo 的 自动持久化查询,前提是 GraphQL-Java 已相应配置

graphQLBuilder.preparsedDocumentProvider(new ApolloPersistedQuerySupport(queryCache));

该处理器同时服务于 GETPOST 请求。但是,您可以将服务限制为一种 HTTP 方法类型

GraphQL graphQL = setupGraphQLJava();

router.post("/graphql").handler(GraphQLHandler.create(graphQL));
GraphQLHandler 需要一个 BodyHandler 来读取 POST 请求内容。

查询批处理

查询批处理是指向 GraphQL 端点发布一个数组而不是单个对象。

Vert.x Web GraphQL 可以处理此类请求,但此功能默认禁用。要启用此功能,请创建带选项的 GraphQLHandler

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

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

基于 WebSocket 的 GraphQL

Vert.x Web GraphQL 与 GraphQL over Websocket 协议兼容。

WebSocket 传输在您需要向 GraphQL 模式添加订阅时特别有用,但您也可以将其用于查询和变更。

默认情况下,配置不包含默认的 Origin 属性。为防止来自 Web 浏览器的跨站 WebSocket 劫持攻击,建议将此属性设置为应用程序面向互联网的源。这将强制检查 WebSocket 源是否来自此应用程序。此检查很重要,因为 WebSockets 不受同源策略的限制,攻击者可以轻易地从恶意网页发起针对 GraphQL WS 处理器 ws://wss:// 端点 URL 的 WebSocket 请求。
router.route("/graphql").handler(GraphQLWSHandler.create(graphQL));

客户端将请求 graphql-transport-ws WebSocket 子协议。因此,必须将其添加到服务器配置中支持的子协议列表中

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

GraphQLWSHandler 支持 Apollo 的 自动持久化查询,前提是 GraphQL-Java 已相应配置

graphQLBuilder.preparsedDocumentProvider(new ApolloPersistedQuerySupport(queryCache));

为了在同一 URI 上同时支持 HTTP 和 WebSockets,必须在 GraphQLHandler 之前将 GraphQLWSHandler 安装到 Router

router.route("/graphql")
  .handler(GraphQLWSHandler.create(graphQL))
  .handler(GraphQLHandler.create(graphQL));
一个 订阅 DataFetcher 必须返回一个 org.reactivestreams.Publisher 实例。

GraphiQL IDE

在您构建应用程序时,在 GraphiQL 中测试 GraphQL 查询会很方便。

为此,为 GraphiQL 资源创建一个路由,并为它们创建一个 GraphiQLHandler

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

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

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

然后浏览到 https://:8080/graphiql/

出于安全原因,GraphiQL 用户界面默认是禁用的。这就是为什么您必须配置 GraphiQLHandlerOptions 来启用它。

当 Vert.x Web 在开发模式下运行时,GraphiQL 会自动启用。要开启开发模式,请使用 VERTXWEB_ENVIRONMENT 环境变量或 vertxweb.environment 系统属性,并将其设置为 dev。在这种情况下,创建 GraphiQLHandler 时无需更改 enabled 属性。

如果您的应用程序受身份验证保护,您可以动态自定义 GraphiQL 发送的请求头

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

GraphiQLHandler handler = GraphiQLHandler.builder(vertx)
  .with(options)
  .addingHeaders(rc -> {
    String token = rc.get("token");
    return MultiMap.caseInsensitiveMultiMap().add("Authorization", "Bearer " + token);
  })
  .build();

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

请参阅 GraphiQLHandlerOptions 文档以获取更多详细信息。

数据获取

GraphQL-Java API 非常适合异步世界:异步执行策略是查询的默认策略(变更的默认策略是串行异步)。

为了 避免阻塞事件循环,您只需实现返回 CompletionStage 而不是直接返回结果的 数据获取器

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();

无需在每个数据获取器实现中手动将 Vert.x Future 转换为 java.util.concurrent.CompletionStage,只需使用 VertxFutureAdapter 插装 来配置 GraphQL-Java。

首先,在配置 GraphQL-Java 时声明该插装。

graphQLBuilder.instrumentation(VertxFutureAdapter.create());

这样您就可以直接返回 Vert.x Future。

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

为数据获取器提供上下文

通常,GraphQLHandlerGraphQLWSHandler 将在其他路由处理器之后声明。例如,您可以使用身份验证来保护您的应用程序。

在这种情况下,您的数据获取器可能需要知道哪个用户已登录,以便缩小结果范围。

为此,您可以通过检查 DataFetchingEnvironment 来检索 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 数据结果

默认的 GraphQL 数据获取器是 PropertyDataFetcher。它无需进一步配置即可读取领域对象的字段。

然而,在 Vert.x 应用程序中,通常会使用 JsonArrayJsonObjectPropertyDataFetcher 可以直接读取 JsonArray 中的项目,但不能读取 JsonObject 的字段。

这个问题的解决方案取决于您的 GraphQL-Java 版本。

两种解决方案都允许您混合使用 JsonObjectJsonArray 和领域对象的结果。

GraphQL-Java 20 及更高版本

使用 JsonObjectAdapter 插装 来配置 GraphQL-Java。

graphQLBuilder.instrumentation(new JsonObjectAdapter());

批量加载

DataLoaders 通过批处理获取请求和缓存结果来帮助您高效地加载数据。

首先,创建一个批量加载器

BatchLoaderWithContext<String, Link> linksBatchLoader = (ids, env) -> {
  // retrieveLinksFromBackend takes a list of ids and returns a CompletionStage for a list of links
  return retrieveLinksFromBackend(ids, env);
};

然后,配置 GraphQLHandler 以便为每个请求创建 DataLoaderRegistry

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 多部分请求 是一种可互操作的多部分表单字段结构,用于 GraphQL 请求。通过启用此功能,GraphQL 客户端将能够使用单个变更调用上传文件。所有服务器端文件处理都将由 GraphQLHandler 抽象。

要启用此功能,请创建一个 GraphQLHandler,将其 requestMultipartEnabled 配置设置为 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);
如果路由器没有 BodyHandler,多部分请求解析器将无法处理 GraphQL 变更调用。

最后,创建 Upload 标量,并将其设置到 RuntimeWiring

RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().scalar(UploadScalar.build()).build();

FileUpload 实例可以通过使用 DataFetchingEnvironment::getArgument 方法来访问。

FileUpload file = environment.getArgument("myFile");