
构建 gRPC Web 服务
本文档将向您展示如何使用 Vert.x 和 gRPC Web 构建一个浏览器/服务器应用程序。
您将构建什么
该应用程序包含一个客户端(浏览器)和一个 Vert.x 服务器。
-
用户在文本字段中输入姓名并点击发送按钮
-
浏览器使用 gRPC Web 协议将姓名发送到服务器
-
服务器回复问候语
-
浏览器显示问候语
在服务器端,您将创建一个 Vert.x gRPC 服务器服务,它:
-
实现 gRPC 服务器存根
-
配置一个 HTTP 服务器,以响应 gRPC Web 和静态文件请求
在客户端,您将创建一个使用 gRPC Web Javascript 客户端的网页。

您需要什么
-
文本编辑器或 IDE
-
Java 17 或更高版本
您无需安装 protoc
或像 vertx-grpc-protoc-plugin2、protobuf-javascript 和 protoc-gen-grpc-web 这样的 protoc 插件,它们将由 Maven 插件管理。
创建项目
gRPC 服务定义
gRPC Greeter
服务包含一个单独的 SayHello
rpc 方法。 HelloRequest
消息包含客户端发送的姓名。 HelloReply
消息包含服务器生成的问候语。
service.proto
文件syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.vertx.howtos.grpcweb";
option java_outer_classname = "HelloWorldProto";
package helloworld;
// The greeting service definition.
service Greeter {
// Ask for a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greeting
message HelloReply {
string message = 1;
}
代码生成
根据服务定义,必须生成几个文件:
-
Java 消息和服务器类
-
Javascript 文件
-
gRPC Web 特定(Javascript)文件。
protoc
的调用由 protobuf-maven-plugin
管理。
protobuf-maven-plugin
进行代码生成<plugin>
<groupId>io.github.ascopes</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<protocVersion>4.29.3</protocVersion>
<sourceDirectories>src/main/proto</sourceDirectories>
<javaEnabled>false</javaEnabled>
</configuration>
<executions>
<execution>
<id>compile-java</id>
<configuration>
<javaEnabled>true</javaEnabled>
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<jvmMavenPlugins>
<jvmMavenPlugin>
<groupId>io.vertx</groupId>
<artifactId>vertx-grpc-protoc-plugin2</artifactId>
<version>${vertx.version}</version>
<mainClass>io.vertx.grpc.plugin.VertxGrpcGenerator</mainClass>
<jvmArgs>
<jvmArg>--grpc-client=false</jvmArg>
<jvmArg>--grpc-service</jvmArg>
<jvmArg>--service-prefix=Vertx</jvmArg>
<jvmArg>--vertx-codegen=false</jvmArg>
</jvmArgs>
</jvmMavenPlugin>
</jvmMavenPlugins>
</configuration>
<goals>
<goal>generate</goal>
</goals>
</execution>
<execution>
<id>compile-javascript</id>
<configuration>
<outputDirectory>${project.basedir}/src/main/web</outputDirectory>
<binaryUrlPlugins>
<binaryUrlPlugin>
<url>${protoc.gen.js.url}</url>
<options>import_style=commonjs</options>
</binaryUrlPlugin>
</binaryUrlPlugins>
</configuration>
<goals>
<goal>generate</goal>
</goals>
</execution>
<execution>
<id>compile-javascript-web</id>
<configuration>
<outputDirectory>${project.basedir}/src/main/web</outputDirectory>
<binaryUrlPlugins>
<binaryUrlPlugin>
<url>${protoc.gen.grpc.web.url}</url>
<options>import_style=typescript,mode=grpcwebtext</options>
</binaryUrlPlugin>
</binaryUrlPlugins>
</configuration>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
1 | 我们选择使用 CommonJS 模块生成方式而不是闭包。 |
服务器端
我们需要一些项目编译所需的依赖项
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-grpc-server</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependencies>
服务器端代码在一个单独的 ServerVerticle
类中。
首先是 gRPC 服务器存根实现。
VertxGreeterGrpcService service = new VertxGreeterGrpcService() {
@Override
public Future<HelloReply> sayHello(HelloRequest request) {
return Future.succeededFuture(HelloReply.newBuilder().setMessage("Hello " + request.getName()).build());
}
};
GrpcServer grpcServer = GrpcServer.server(vertx);
grpcServer.addService(service);
这里没有什么与 gRPC Web 特别相关的内容。
GrpcServer 默认启用 gRPC Web 协议支持。 |
然后我们必须配置一个 Vert.x Web Router
以接受 gRPC Web 和静态文件请求。
Router router = Router.router(vertx);
router.route()
.consumes("application/grpc-web-text") (1)
.handler(rc -> grpcServer.handle(rc.request()));
router.get().handler(StaticHandler.create()); (2)
return vertx.createHttpServer()
.requestHandler(router)
.listen(8080);
1 | 所有内容类型为 application/grpc-web-text 的请求都将交由 grpcServer 处理。 |
2 | 所有其他 GET 请求将由 Vert.x Web StaticHandler 处理。 |
客户端
在编写代码之前,我们必须设置项目以构建客户端代码。为了简单起见,我们选择使用 esbuild-maven-plugin
。简而言之,它是一个 Maven 插件,封装了 esbuild
,一个用于 Web 的快速打包工具。
需要几个依赖项,我们通过 mvnpm
将它们作为 Maven 依赖项获取:
esbuild-maven-plugin
构建客户端代码<plugin>
<groupId>io.mvnpm</groupId>
<artifactId>esbuild-maven-plugin</artifactId>
<version>0.0.2</version>
<executions>
<execution>
<id>esbuild</id>
<goals>
<goal>esbuild</goal>
</goals>
</execution>
</executions>
<configuration>
<entryPoint>index.js</entryPoint>
<outputDirectory>${project.build.outputDirectory}/webroot/js</outputDirectory> (1)
</configuration>
<dependencies>
<dependency>
<groupId>org.mvnpm</groupId>
<artifactId>grpc-web</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.mvnpm</groupId>
<artifactId>google-protobuf</artifactId>
<version>3.21.4</version>
</dependency>
</dependencies>
</plugin>
1 | webroot 是 Vert.x Web StaticHandler 提供静态文件的默认基本目录。 |
用户界面代码在一个单独的 index.html
文件中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Echo Example</title>
<script type="module">
import {sayHello} from "/js/index.js"; (1)
window.sayHello = sayHello; (2)
</script>
</head>
<body>
<div>
<p>Type a name in the input field and press enter, or click the send button.</p>
<div class="input-group">
<!-- Invoke javascript function on submit -->
<form onsubmit="return sayHello();">
<input type="text" id="name">
<input type="submit" value="Send">
</form>
</div>
<p id="msg"></p>
</div>
</body>
</html>
1 | 从我们的 Javascript 模块中导入 sayHello 函数(见下文)。 |
2 | 使 sayHello 函数成为全局函数。 |
最后但同样重要的是,让我们实现 sayHello
函数:
const {HelloRequest} = require("./service_pb"); (1)
const {GreeterClient} = require("./service_grpc_web_pb"); (2)
const greeterClient = new GreeterClient("http://" + window.location.hostname + ":8080", null, null); (3)
export function sayHello() {
const request = new HelloRequest();
request.setName(document.getElementById("name").value);
greeterClient.sayHello(request, {}, (err, response) => {
const msgElem = document.getElementById("msg");
if (err) {
msgElem.innerText = `Unexpected error for sayHello: code = ${err.code}` + `, message = "${err.message}"`;
} else {
msgElem.innerText = response.getMessage();
}
});
return false; // prevent form posting
}
1 | 从 Javascript 生成的文件中导入 HelloRequest 对象。 |
2 | 从 gRPC Web (Javascript) 生成的文件中导入 GreeterClient 对象。 |
3 | 配置客户端以向 Web 服务器发送请求。 |
运行应用程序
您可以使用 Maven 运行应用程序
./mvnw compile exec:java
您应该看到:
Server started, browse to https://:8080
您现在可以浏览到 https://:8080 并按照说明操作。
使用浏览器的开发工具检查 gRPC Web 流量。

总结
本文档涵盖了
-
实现一个响应静态文件和 gRPC Web 请求的 Vert.x web 服务器,
-
创建一个使用 gRPC Web 客户端的网页。