Vert.x Web GraphQL

Vert.x Web GraphQL 用 GraphQL-Java 库扩展了 Vert.x Web,您可以用它创建一个 GraphQL 的服务器。

提示
这是一个 Vert.x Web GraphQL 的参考文档。 强烈建议您先熟悉一下 GraphQL-Java API。 您可以从此处开始阅读 GraphQL-Java documentation

由此开始

若要使用该模块,请在您的 Maven POM 文件的 dependencies 部分中添加以下依赖:

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

或您使用的是Gradle:

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

设置处理器

HTTP

为此您需要创建 Vert.x Web Route 以及 GraphQLHandler

GraphQL graphQL = setupGraphQLJava();

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

The GraphQLHandler supports Apollo’s automatic persisted queries, provided GraphQL-Java is configured accordingly:

graphQLBuilder.preparsedDocumentProvider(new ApolloPersistedQuerySupport(queryCache));

该处理器可处理 GET 以及 POST 请求。 当然,您也可以限制处理器仅处理其中一种 HTTP 方法:

GraphQL graphQL = setupGraphQLJava();

router.post("/graphql").handler(GraphQLHandler.create(graphQL));
重要
GraphQLHandler 需要 BodyHandler ,用于读取 POST 请求体内容。

查询批处理

查询批处理将数组(而非单个对象)通过 post 方法发送到 GraphQL 端点(endpoint)。

Vert.x Web GraphQL 可以处理此类请求,但该特性默认是被禁用的。 如要启用,需创建带参数的 GraphQLHandler

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

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

基于 WebSocket 的 GraphQL

Vert.x Web GraphQL 是兼容 基于 Websocket 协议的 GraphQL 的。

当您需要给您的 GraphQL schema 添加订阅时使用 websocket 进行通信非常有用,当然您也可以用其来进行查询和变更操作。

小心
默认情况下, 配置中并不包含一个默认的 Origin 属性。 为了防御浏览器发送的 WebSocket 跨域劫持攻击,我们建议您将 Origin 属性设置为您应用的网络源。 这会强制服务器检查 WebSocket 的 Origin 以验证连接是否来自您的应用。 这项检查非常重要,因为浏览器的同源策略并不限制 WebSocket 连接,因此一个攻击者可以轻松地从一个恶意网页连接您服务器上 GraphQL WS 处理器提供的 ws://wss:// 接口。
router.route("/graphql").handler(GraphQLWSHandler.create(graphQL));
重要

客户端会要求支持 graphql-transport-ws websocket 子协议。 因此,您需要在服务端的配置中将该协议加入到支持的子协议列表中:

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

The GraphQLWSHandler supports Apollo’s automatic persisted queries, provided GraphQL-Java is configured accordingly:

graphQLBuilder.preparsedDocumentProvider(new ApolloPersistedQuerySupport(queryCache));

为了让 HTTP 协议和 WebSocket 协议使用同一个 URI,您必须在 GraphQLHandler 之前将 GraphQLWSHandler 注册到 Router 上:

router.route("/graphql")
  .handler(GraphQLWSHandler.create(graphQL))
  .handler(GraphQLHandler.create(graphQL));
重要
一个 DataFetcher 订阅 会返回一个 org.reactivestreams.Publisher 类型的实例。

GraphiQL IDE

As you are building your application, testing your GraphQL queries in GraphiQL can be handy.

To do so, create a route for GraphiQL resources and a GraphiQLHandler for them:

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

router.route("/graphiql/*").handler(GraphiQLHandler.create(options));
注意
The GraphiQL user interface is disabled by default for security reasons. This is why you must configure the GraphiQLHandlerOptions to enable it.
提示

GraphiQL is enabled automatically when Vert.x Web runs in development mode. To switch the development mode on, use the VERTXWEB_ENVIRONMENT environment variable or vertxweb.environment system property and set it to dev. In this case, create the GraphiQLHandler without changing the enabled property.

If your application is protected by authentication, you can customize the headers sent by GraphiQL dynamically:

graphiQLHandler.graphiQLRequestHeaders(rc -> {
  String token = rc.get("token");
  return MultiMap.caseInsensitiveMultiMap().add("Authorization", "Bearer " + token);
});

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

Please refer to the GraphiQLHandlerOptions documentation for further details.

获取数据

GraphQL-Java API 非常适合异步环境:查询操作默认策略便是异步执行(对于变更,则是串行异步)。

避免阻塞 eventloop ,您需要做的是实现 data fetchers 并返回 CompletionStage ,而不是直接返回结果。(译者注: Data Fetcher 下文统一保留原文、不翻译)

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

Instead of converting Vert.x Future to java.util.concurrent.CompletionStage manually in every data fetcher implementation, configure GraphQL-Java with the VertxFutureAdapter instrumentation.

First, declare the instrumentation while configuring GraphQL-Java.

graphQLBuilder.instrumentation(VertxFutureAdapter.create());

Then you can return Vert.x futures directly.

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

为数据获取器提供上下文

GraphQLHandler 通常是最后一个声明的处理器。 例如,您可以通过身份验证保护您的应用程序。

在这种情况下,您的 DataFetcher 可能需要知道哪个用户已登录,才能缩小结果范围。 假设您的身份验证层在 RoutingContext 中存储了一个 User 对象。

您可以通过 DataFetchingEnvironment 来获取这个 User 对象:

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

  RoutingContext routingContext = GraphQLHandler.getRoutingContext(environment.getGraphQlContext());

  User user = routingContext.get("user");

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

};

JSON 类型数据结果

The default GraphQL data fetcher is the PropertyDataFetcher. It is able to read the fields of your domain objects without further configuration.

Nevertheless, in Vert.x applications it is common to work with JsonArray and JsonObject. The PropertyDataFetcher can read the items in a JsonArray out of the box, but not the fields of a JsonObject.

The solution to this problem depends on your GraphQL-Java version.

注意
Both solutions let you mix JsonObject, JsonArray and domain objects results.

GraphQL-Java 20 and later

Configure GraphQL-Java with the JsonObjectAdapter instrumentation.

graphQLBuilder.instrumentation(new JsonObjectAdapter());

GraphQL-Java 19 and before

Configure GraphQL-Java to use VertxPropertyDataFetcher instead:

RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();

builder.wiringFactory(new WiringFactory() {

  @Override
  public DataFetcher<Object> getDefaultDataFetcher(FieldWiringEnvironment environment) {

    return VertxPropertyDataFetcher.create(environment.getFieldDefinition().getName());

  }
});

批量加载

DataLoader 可以批量化请求并缓存结果,助您高效地加载数据。(译者注:与 DataFetcher 对应,DataLoader保留原文不翻译)

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

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.create(graphQL).beforeExecute(builderWithContext -> {

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

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

  builderWithContext.builder().dataLoaderRegistry(dataLoaderRegistry);

});

文件上传

GraphQL multipart 请求 是用于 GraphQL 请求的可互操作表单结构。 通过启用此功能,GraphQL 客户端将能够使用单个变更(Mutation)调用来上传文件。 所有服务器端文件处理逻辑都抽象到了 GraphQLHandler

启用该功能需要创建一个 GraphQLHandler ,并将 requestMultipartEnabled 配置设置为true,然后添加 BodyHandler 到路由。

GraphQLHandler graphQLHandler = GraphQLHandler.create(
  setupGraphQLJava(),
  new GraphQLHandlerOptions().setRequestMultipartEnabled(true)
);

Router router = Router.router(vertx);

router.route().handler(BodyHandler.create());
router.route("/graphql").handler(graphQLHandler);
重要
如果路由没有 BodyHandler ,则 multipart 请求解析器无法处理 GraphQL 的变更请求。

最后,创建 Upload scalar 并设置到 RuntimeWiring 中:

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

FileUpload 实例可以通过 DataFetchingEnvironment::getArgument 方法获取。

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

RxJava 3 API

配置 Rxified 路由

若需要在 Rxified 路由 中处理 GraphQL 请求,则请确保已导入了 GraphQLHandler 类。

使用 Vert.x 的 Rxified API

GraphQL-Java 期望 DataFetcher 和批量加载器使用 CompletionStage 提供异步结果。

但是若您使用 Vert.x Rxified API (如 Web 客户端),则需要适配 SingleMaybe 对象。

DataFetcher<CompletionStage<String>> fetcher = environment -> {
 Single<String> data = loadDataFromBackend();
 return data.toCompletionStage();
};
提示

Instead of converting Single or Maybe to CompletionStage manually in every data fetcher implementation, configure GraphQL-Java with the SingleAdapter and MaybeAdapter instrumentations.

First, declare the instrumentations while configuring GraphQL-Java.

graphQLBuilder.instrumentation(new ChainedInstrumentation(SingleAdapter.create(), MaybeAdapter.create()));

Then you can return Single or Maybe directly.

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