使用protobuf前后端通信

浏览器客户端和服务端之间通信大多使用XML或JSON作为数据交换格式,不过实际上我们也可以使用ProtoBuf来通信。虽然ProtoBuf可能不如XML或JSON简单和易于调试,但它在序列化数据的体积和序列化性能方面会有一些优势,这里我们还是以Java后端工程为例简单介绍一下。

仅使用ProtoBuf序列化

一个简单的方案是仅使用ProtoBuf代替XML或JSON做数据的序列化,HTTP服务、拦截器、路由等功能仍然由SpringMVC实现,这种方式比较适合在已有工程中添加新的HTTP接口,新添加的ProtoBuf的接口也能继续使用原来SpringMVC相关的基础功能。这里我们以SpringBoot工程为例进行介绍,前端工程则使用React实现。

服务端工程

在服务端工程中,所需的Maven依赖和前一篇笔记服务端中的是相同的,即net.devh.grpc-spring-boot-starter以及相应的Maven插件,我们需要将.proto文件编译成Java代码,有关这些内容可以参考前一篇笔记,这里就不重复黏贴了。

除此之外,后端工程中,我们还需要配置SpringMVC的MessageConverter,使SpringMVC在内容协商阶段知道该如何处理ProtoBuf格式的数据。

package com.gacfox.demo.rpcservice.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;

@Configuration
public class MessageConverterConfig {
    @Bean
    public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufHttpMessageConverter();
    }
}

此时我们就可以直接使用了,下面例子定义了一个Controller,它接收和返回ProtoBuf格式的数据。

package com.gacfox.demo.rpcservice.controller;

import com.gacfox.demo.api.DemoProtocol;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/api/v1")
public class GrpcController {
    @PostMapping(value = "/demo", produces = "application/x-protobuf")
    public DemoProtocol.DemoRsp demo(@RequestBody DemoProtocol.DemoReq demoReq) {
        // ...具体业务处理
        log.info("Req: {}", demoReq.getMsg());
        return DemoProtocol.DemoRsp.newBuilder().setMsg("成功").build();
    }
}

注意我们前端传过来的请求数据是在Body中发送的,因此需要使用@RequestBody注解,此外控制器方法中我们标注了produces = "application/x-protobuf"表示该控制器返回的是ProtoBuf数据,其余和正常使用JSON没什么区别。

前端工程

前端工程需要用到两个包,protobufjs提供了对ProtoBuf数据格式的支持,protobufjs-cli是一个命令行工具,它可以将.proto文件编译成JavaScript代码供我们调用,它的作用和前面后端工程中的Maven插件类似。

项目Github地址:https://github.com/protobufjs/protobuf.js

npm install protobufjs protobufjs-cli --save-dev

我们这里使用Vite搭建一个React工程,主要目录结构如下。

|_src
  |_proto
    |_demo_service.proto
  |_App.jsx
|_vite.config.js
|_index.html
|_package.json

其中我们主要关注src/proto文件夹,我们将所有的.proto文件都放在这个文件夹下。

此外我们还需要在package.json中添加以下命令,命令中我们调用protobufjs-cli.proto文件编译成对应的JavaScript代码。

"proto": "pbjs -t static-module -w es6 -o src/proto/proto.js src/proto/*.proto"

-t参数指定了编译的目标,我们这里编译为静态代码模块,-w参数指定编译结果的引入方式,我们这里指定使用ES6模块引入,-o指定编译输出为一个proto.js文件。有关参数的其他内容可以参考相关文档。

前端调用后端接口

下面代码是一个React组件,其中使用FetchAPI发送了一个请求,但它和一般我们熟悉的JSON请求不同,它的Body实际上是一个Uint8Array类型的数据,我们要传递的数据经过protobufjs转换成了二进制数据格式并放在请求体中发送给服务端;接收返回时,我们也是使用Uint8Array类型接收,并重新反序列化为JavaScript对象。

import { DemoReq, DemoRsp } from "@/proto/proto";
import { useEffect, useState } from "react";

const App = () => {
  const fetchData = async () => {
    const reqData = DemoReq.create({
      msg: "Hello, protobuf.",
    });
    const reqBuffer = DemoReq.encode(reqData).finish();
    const response = await fetch("/api/v1/demo", {
      method: "POST",
      body: reqBuffer,
      headers: { "Content-Type": "application/x-protobuf" },
    });
    const rspBuffer = await response.arrayBuffer();
    return DemoRsp.decode(new Uint8Array(rspBuffer));
  };

  const [data, setData] = useState({});

  useEffect(() => {
    fetchData().then((data) => {
      setData(data);
    });
  }, []);

  return <div>{JSON.stringify(data)}</div>;
};

export default App;

使用gRPC-Web

gRPC-Web是一个专用于服务端和浏览器端通信的完整gRPC协议实现,它和我们常说的gRPC over HTTP2略有不同(因为浏览器不直接支持gRPC协议),不过底层都是使用ProtoBuf进行通信。gRPC-Web需要配合专用的反向代理才能使用,Envoy可以通过配置过滤器支持该功能,Nginx则需要ngx_http_grpc_module模块。

gRPC-Web项目Github地址:https://github.com/grpc/grpc-web

Nginx的gRPC模块项目Github地址:https://nginx.org/en/docs/http/ngx_http_grpc_module.html

gRPC-Web目前使用较少,我们可以参考相关文档,这里就不多介绍了。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap