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.2.7</version>
</dependency>
或您使用的是Gradle:
compile 'io.vertx:vertx-web-graphql:4.2.7'
设置处理器
HTTP
为此您需要创建 Vert.x Web Route
以及 GraphQLHandler
:
GraphQL graphQL = setupGraphQLJava();
router.route("/graphql").handler(GraphQLHandler.create(graphQL));
该处理器可处理 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);
GraphiQL IDE
在构建应用程序时,可以在 GraphiQL 中很方便地测试您的 GraphQL 查询。
为此,您需要为 GraphiQL 资源创建一个路由(route)及相应的处理器 GraphiQLHandler
GraphiQLHandlerOptions options = new GraphiQLHandlerOptions()
.setEnabled(true);
router.route("/graphiql/*").handler(GraphiQLHandler.create(options));
随后浏览以下地址 http://localhost:8080/graphiql/ 。
注意
|
出于安全考虑,默认情况下 GraphiQL 用户界面是禁用的。
所以需要配置 GraphiQLHandlerOptions 才能启用 GraphiQL 用户界面。
|
提示
|
当 Vert.x Web 在开发模式下运行时,将自动启用 GraphiQL。
要启用开发模式,请将 VERTXWEB_ENVIRONMENT 环境变量或 vertxweb.environment 系统属性设置为 dev 。
在这种情况下,创建 GraphiQLHandler 时,无需设置 GraphiQLHandlerOptions 的 enabled 属性。
|
如果您的应用程序受身份验证保护,则可以动态自定义 GraphiQL 发送的标头(headers):
graphiQLHandler.graphiQLRequestHeaders(rc -> {
String token = rc.get("token");
return MultiMap.caseInsensitiveMultiMap().add("Authorization", "Bearer " + token);
});
router.route("/graphiql/*").handler(graphiQLHandler);
请参考 GraphiQLHandlerOptions
文档以获取更多详细信息。
基于 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));
重要
|
客户端会要求支持
|
为了让 HTTP 协议和 WebSocket 协议使用同一个 URI,您必须在 GraphQLHandler
之前将 GraphQLWSHandler
注册到 Router
上:
router.route("/graphql")
.handler(GraphQLWSHandler.create(graphQL))
.handler(GraphQLHandler.create(graphQL));
重要
|
一个 DataFetcher 订阅 会返回一个 org.reactivestreams.Publisher 类型的实例。
|
Apollo WebSocketLink
您可以使用 Apollo WebSocketLink 连接 websocket。 这对订阅 GraphQL schema 特别有帮助,但您亦可以使用 websocket 查询和变更。(译者注:GraphQL的三种操作类型的翻译及原文:查询 Query,变更 Mutation以及订阅 Subscription)
GraphQL graphQL = setupGraphQLJava();
router.route("/graphql").handler(ApolloWSHandler.create(graphQL));
重要
|
想要支持 graphql-ws websocket 子协议,必需在服务器配置中添加:
|
HttpServerOptions httpServerOptions = new HttpServerOptions()
.addWebSocketSubProtocol("graphql-ws");
vertx.createHttpServer(httpServerOptions)
.requestHandler(router)
.listen(8080);
注意
|
如果要在同一路径中支持 WebSocketLink 和 HttpLink ,您可先添加 ApolloWSHandler ,然后再添加 GraphQLHandler 。 |
GraphQL graphQL = setupGraphQLJava();
router.route("/graphql").handler(ApolloWSHandler.create(graphQL));
router.route("/graphql").handler(GraphQLHandler.create(graphQL));
您可在此处找到配置 Apollo 订阅客户端(SubscriptionClient)的方法: https://github.com/apollographql/subscriptions-transport-ws
重要
|
一个 DataFetcher 订阅必需返回一个 org.reactivestreams.Publisher 实例。
|
获取数据
GraphQL-Java API 非常适合异步环境:查询操作默认策略便是异步执行(对于变更,则是串行异步)。
为 避免阻塞 eventloop ,您需要做的是实现 data fetchers 并返回 CompletionStage
,而不是直接返回结果。(译者注: Data Fetcher 下文统一保留原文、不翻译)
DataFetcher<CompletionStage<List<Link>>> dataFetcher = environment -> {
CompletableFuture<List<Link>> completableFuture = new CompletableFuture<>();
retrieveLinksFromBackend(environment, ar -> {
if (ar.succeeded()) {
completableFuture.complete(ar.result());
} else {
completableFuture.completeExceptionally(ar.cause());
}
});
return completableFuture;
};
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
.build();
使用基于回调的API
实现一个返回 CompletionStage
的 DataFetcher 并不复杂。
但当您使用基于回调的 Vert.x API 时,需要一些八股文代码。
这时可以参考 VertxDataFetcher
获取帮助:
VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create((env, promise) -> {
retrieveLinksFromBackend(env, promise);
});
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
.build();
使用 Vert.x Future
VertxDataFetcher
对于使用 Future 化的 API 也有帮助:
VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create(environment -> {
Future<List<Link>> future = retrieveLinksFromBackend(environment);
return future;
});
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
.build();
为数据获取器提供上下文
GraphQLHandler
通常是最后一个声明的处理器。
例如,您可以通过身份验证保护您的应用程序。
在这种情况下,您的 DataFetcher 可能需要知道哪个用户已登录,才能缩小结果范围。
假设您的身份验证层在 RoutingContext
中存储了一个 User
对象。
您可以通过 DataFetchingEnvironment
来获取这个 User
对象:
VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create((environment, promise) -> {
RoutingContext routingContext = GraphQLHandler.getRoutingContext(environment.getGraphQlContext());
User user = routingContext.get("user");
retrieveLinksPostedBy(user, promise);
});
注意
|
RoutingContext 可用于任何类型的 DataFetcher ,而不仅仅是 VertxDataFetcher 。
|
JSON 类型数据结果
默认的GraphQL DataFetcher 是 PropertyDataFetcher
。
因此,它无需进一步配置即可读取域对象的字段。
不过,某些 Vert.x 数据客户端会返回 JsonArray
和 JsonObject
类型的结果。
如果您不需要(或不想)使用域对象层,则可以将 GraphQL-Java 配置改为使用 VertxPropertyDataFetcher
:
RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();
builder.wiringFactory(new WiringFactory() {
@Override
public DataFetcher<Object> getDefaultDataFetcher(FieldWiringEnvironment environment) {
return VertxPropertyDataFetcher.create(environment.getFieldDefinition().getName());
}
});
提示
|
VertxPropertyDataFetcher 包装了一个 PropertyDataFetcher ,因此您也可以通过它使用域对象。
|
批量加载
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);
});
如果您使用了 Vert.x 的 API,那么您可以使用 VertxBatchLoader
或 VertxMappedBatchLoader
来简化代码:
BatchLoaderWithContext<Long, String> commentsBatchLoader = VertxBatchLoader.create((ids, env) -> {
// findComments takes a list of ids and returns a Future for a list of links
return findComments(ids, env);
});
文件上传
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 客户端 或 Cassandra 客户端),则需要适配 Single
和 Maybe
对象。
RxJavaJdk8Interop
库提供了这些适配工具。
您可以在 Maven 构建配置文件的 dependencies 部分中增加以下依赖:
<dependency>
<groupId>com.github.akarnokd</groupId>
<artifactId>rxjava3-jdk8-interop</artifactId>
<version>3.0.0-RC6</version>
</dependency>
如果您使用 Gradle,则增加以下依赖配置:
implementation 'com.github.akarnokd:rxjava3-jdk8-interop:3.0.0-RC6'
然后可以从 Single
结果中创建 DataFetcher :
Single<String> data = loadDataFromBackend();
DataFetcher<CompletionStage<String>> fetcher = environment -> {
return data.to(SingleInterop.get());
};
同样,可以使用 MaybeInterop
处理 Maybe
结果。