RSS

gRPC 能否取代 REST 和 WebSockets 进行 Web 应用程序通信?

在快速发展的 Web 开发领域,效率和性能通常是采用新技术的最前沿。 gRPC-Web 库正在进行的工作标志着开发人员如何利用 gRPC 的速度和力量进入 Web 应用程序中的客户端-服务器通信,从而取代 REST 和 WebSockets 的某些方面。让我们看一下与传统 RESTful 调用和 WebSocket 连接的比较分析,并提供 gRPC-Web 的实际代码示例,以比较每种方法。

了解 gRPC-Web 及其在现代 Web 开发中的地位

gRPC-Web 的起源在于追求更具响应性、低延迟的 Web 应用程序。它将 gRPC(一种高性能、开源的通用 RPC 框架)的功能扩展到浏览器,从而能够与通常指定用于服务器到服务器通信的 gRPC 服务直接通信。gRPC 基于一种名为 协议缓冲区 (Protobuf) 的序列化格式构建,这有助于减少有效负载和明确定义的接口描述,从而简化开发过程。

在深入研究技术细节之前,让我们先研究一下 gRPC-Web 所代表的潜在转变。与需要 HTTP/2 的原生 gRPC 协议不同,gRPC-Web 放宽了此要求,使其可以支持浏览器环境中可用的任何 HTTP/* 协议。在 WebSocket 场景中,会维护持久连接以进行全双工通信,但 WebSockets 可能会在管理各种连接状态时引入复杂性。gRPC-Web 通过其服务器流功能提供了一种引人注目的替代方案,从而实现了更高效的实时数据流。目前,由于浏览器限制,gRPC-Web 不支持客户端流。

gRPC-Web 的机制:它是如何工作的

要将 gRPC-Web 集成到您的 Web 应用程序中,必须采用特定的架构。此架构的核心是 Envoy 代理,它充当 Web 应用程序和 gRPC 服务器之间的桥梁(这是必要的,以允许网络协议的抽象)。Envoy 将 gRPC-Web 调用转换为 gRPC 调用,处理 HTTP/1.1 到 HTTP/2 的转换,使浏览器可以享受 gRPC 的好处。

让我们分解此通信模型中涉及的步骤

  1. 浏览器启动 gRPC-Web 客户端调用。
  2. Envoy 代理接收调用,其中包括 Protobuf 定义的请求。
  3. 然后,Envoy 将其转换为 HTTP/2 gRPC 调用并将其转发到 gRPC 服务器。
  4. gRPC 服务器处理请求并将响应返回给 Envoy。
  5. Envoy 将 gRPC 响应转换回 gRPC-Web 格式并发送给客户端。

通过这种代理系统,gRPC-Web 促进了强大的客户端-服务器交互,该交互具有高性能,并由于 Protobuf 的强数据类型而提供了数据清晰度和精度。

从 REST 过渡到 gRPC-Web 的理论

对于习惯使用 REST 的开发人员来说,跳转到 gRPC-Web 可能看起来具有挑战性。如果对所涉及的组件有适当的了解并采取逐步的方法,则过渡可以很顺利。

考虑一个典型的 RESTful 获取调用

fetch('https://api.example.com/data', {
    method: 'GET',
    headers: {
        'Accept': 'application/json',
    },
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

上面的代码从 RESTful API 服务检索 JSON 数据。请注意 fetch API、HTTP 方法和内容类型标头的使用。响应被处理为 JSON 对象,并且错误处理被嵌入到 Promise 链中。此代码中未指示的是数据验证,该验证是确保您在有效负载中接收的数据与预期模式匹配,并且模式中接收的数据正确设置为您需要的数据类型,以及处理错误数据的用户体验。

让我们用 gRPC-Web 重新构想一下

const { ExampleRequest, ExampleResponse } = require('./generated/example_pb.js');
const { ExampleServiceClient } = require('./generated/example_grpc_web_pb.js');

const client = new ExampleServiceClient('https://api.example.com');

const request = new ExampleRequest();

client.getExampleData(request, {}, (err, response) => {
    if (err) {
        console.error('Error:', err);
    } else {
        console.log(response.toObject());
    }
});

在这个 gRPC-Web 示例中,我们首先导入必要的 Protobuf 定义和客户端存根。创建客户端实例,指定服务 URL。我们构造一个请求对象,并在客户端上调用 getExampleData 方法,传递请求和一个回调函数来处理响应或错误。

请注意方法上的明显差异:gRPC-Web 调用是强类型的,并且序列化/反序列化由库处理,而不是由开发人员手动处理。这种类型安全性和自动化可以大大减少人为错误的潜在性并简化开发过程。如果您收到一个对象,则该对象已经过完全验证。

gRPC-Web 相对于 REST 的优势

虽然 REST 多年来一直是 Web API 的基石,但当涉及到复杂的 Web 应用程序时,它的简单性有时会成为一种限制。虽然 gRPC-Web 可以与浏览器中支持的任何 HTTP/* 协议一起使用,但 gRPC-Web 利用了许多 HTTP/2 功能,带来了一系列改进。以下是 HTTP/2 和 gRPC-Web 的一些优点

  • 它可以与现有服务一起使用:除了 Envoy 代理之外,无需构建任何新东西,因此实施 gRPC-Web 将允许您访问任何现有的 gRPC 服务。这对于使用 JavaScript 库的应用程序(包括移动应用程序)可能是一个优势。
  • 类型安全: 使用 gRPC-Web 时,请求和响应都基于 Protobuf 定义进行强类型化。客户端和服务器之间的这种契约是明确的,减少了沟通不畅和错误的可能性。
  • 高效的序列化: Protobuf(gRPC 使用的序列化格式)比 JSON 或 XML 更高效,从而可以更快地进行序列化并减小消息大小。这对于性能尤其有益,并且可以节省带宽方面的成本。HTTP/1.1 允许以文本模式或二进制模式发送数据,但不能同时发送这两种模式。HTTP/2 仅是二进制的,并且将二进制编码/解码为文本比将二进制文件编码/解码为文本以通过 REST 发送混合有效负载的错误更少。
  • 清晰的 API 契约: 使用 Protobuf 进行服务定义会创建一个清晰的、与语言无关的 API 契约。这可以用来生成多种语言的客户端和服务器代码,从而为开发人员提供无缝的体验。

设置 gRPC-Web 环境

开始使用 gRPC-Web 需要使用 Protobuf 定义服务和消息有效负载,设置 gRPC 后端服务(或目前是模拟服务器),并配置 Envoy 代理以在 gRPC-Web 和 gRPC 之间进行转换。

首先,您在 .proto 文件中定义您的服务

syntax = "proto3";

package example;

service ExampleService {
  rpc GetExampleData(ExampleRequest) returns (ExampleResponse);
}

message ExampleRequest {
  string query = 1;
}

message ExampleResponse {
  repeated string data = 1;
}

.proto 文件定义了一个简单的服务,该服务具有一个 RPC 方法 GetExampleData,以及请求和响应消息格式。由于该操作在请求中发送单个 ExampleRequest 消息,并期望在响应中接收单个 ExampleResponse 消息,因此此一元 RPC 调用模仿了 RESTful 请求。

接下来,使用带有适当 gRPC-Web 插件的 protoc 命令行工具为您的服务生成客户端存根代码。(这是来自 gRPC-Web 快速入门文档的一个示例)。此过程将创建您需要用来从浏览器进行 gRPC-Web 调用的 JavaScript 客户端文件。

在您以您选择的语言实现 gRPC 服务器后,您将配置一个 Envoy 代理。这是来自 gRPC-Web 快速入门文档的另一个示例

以下是一些 Envoy 配置的 YAML 语法,它启用了 gRPC-Web 作为上面链接的较大配置的一部分。

http_filters:
- name: envoy.filters.http.grpc_web
- name: envoy.filters.http.router

有了这些要素,您可以开始从 Web 应用程序进行 gRPC-Web 调用。

使用 Protobuf 定义服务方法

在定义服务方法时,Protobuf 通过定义请求和响应的消息结构来充当单一的事实来源。这种严格的架构允许在多种语言中自动生成客户端和服务器代码。特别是对于 JavaScript,此代码生成简化了浏览器客户端的调用过程。

使用上面的示例 .proto 文件,生成的 JavaScript 客户端代码将使用这些定义来确保仅发送和接收正确的数据类型。此过程处理了大量手动数据验证和解析,而这些验证和解析在使用 RESTful 服务时很容易出错。

使用 gRPC-Web 替换典型的 WebSocket 连接

WebSocket 通过单个持久连接提供全双工通信通道。在 gRPC-Web 由于其缺少客户端流功能而无法完全替代 WebSocket 的情况下,它仍然可以用于高效的服务器到客户端流。

这是一个典型的 WebSocket 实现示例

const socket = new WebSocket('ws://example.com/data');

socket.onmessage = function(event) {
  const receivedData = JSON.parse(event.data);
  console.log(receivedData);
};

socket.onerror = function(error) {
  console.error('WebSocket Error:', error);
};

WebSocket API 很简单,但是管理连接的状态和生命周期可能会变得很复杂。

现在,让我们探讨一下使用 gRPC-Web 的服务器端流如何实现

const { Empty } = require('./generated/common_pb.js');
const { DataServiceClient } = require('./generated/data_grpc_web_pb.js');

const client = new DataServiceClient('https://api.example.com');

const request = new Empty();

const stream = client.dataStream(request, {});

stream.on('data', (response) => {
  console.log(response.toObject());
});

stream.on('error', (err) => {
  console.error('Stream Error:', err);
});

stream.on('end', () => {
  console.log('Stream ended.');
});

虽然使用 gRPC-Web 替换 WebSocket 涉及更多代码,但您可以设置服务器端流调用,其中服务器可以连续向客户端发送消息。客户端使用事件侦听器来处理传入的消息、错误和流的结束。它与 WebSocket 的范例不同,但在支持的用例中,它可能更高效且更易于管理。

通过 WebSocket 的许多聊天应用程序都利用单个客户端发送和服务器流式传输事件,gRPC-Web 可以替换这些事件。即使在开发多人游戏的场景中,RPC 调用“MoveCharacters”也可以从浏览器接收一条消息,其中您移动您的角色,并流回其他玩家或计算机控制的角色的所有移动。

现在是时候替换 REST 和 WebSockets 了吗?

本文开始从表面上探讨如何使用 gRPC-Web 替换 REST 和 WebSocket,重点介绍这样做的原因以及如何开始使用实际代码示例。还需要做更多工作来完全整合错误处理,并展示性能基准,这超出了本文档的范围。

在现代 Web 应用程序开发中,gRPC 和 gRPC-Web 的许多技术方面,结合 Envoy,可以取代 REST 和 WebSockets。虽然目前面向公众的 gRPC API 很少,但我们很快就会看到更多公司采用基于 HTTP/2 和 HTTP/3 的高性能 API,并考虑用于 Web 应用程序的其他新兴技术。

Postman 中的 gRPC 支持

如果您使用 API,您可能经常使用 Postman。您知道 Postman 支持 gRPC 了吗? 我们的 VS Code 扩展也支持 gRPC 请求,方便您在构建应用程序时使用。今年我们参加了 gRPC 大会,并与社区进行了交流,收获颇丰。请密切关注我们的博客,了解有关 gRPC 的最新动态和文章。如果您想了解关于 gRPC、Protobuf 以及它们在 Postman 中的使用方式的历史,您也可以查看我们的 Postman Academy 课程

技术审阅:Kevin Swiber (Postman), Eryu Xia (Google)