【Golang】实现一个 HTTP 反向署理服务器

打印 上一主题 下一主题

主题 1976|帖子 1976|积分 5928

在企业应用开发中,假如需要对外提供接口,最好的方式是提供 HTTP 接口。为了制止重新实现一套 HTTP 服务代码,建议使用 grpc-gateway 包,将 HTTP 请求转化为 gRPC 请求,以完全复用 gRPC 接口的请求处理逻辑。
grpc-gateway 介绍

grpc-gateway 是 protoc 的一个插件。它读取 gRPC 服务定义,并生成反向署理服务器(Reverse Proxy)。反向署理服务器根据 gRPC 服务定义中的 google.api.http 注释生成,能够将 RESTful JSON API 转换为 gRPC 请求,从而实现同时支持 gRPC 客户端和 HTTP 客户端调用 gRPC 服务的功能。

传统 gRPC 应用常创建客户端与服务交互,此场景则借助 grpc - gateway 构建反向署理服务。该署理为 gRPC 远程方法袒露 RESTful API,接收 REST 客户端 HTTP 请求,将其转为 gRPC 消息调用后端服务远程方法,再把后端响应转换为 HTTP 响应发回客户端。
为什么需要 gRPC-Gateway

GO 项目开发中,提升接口性能并便于内部系统之间的接口调用,通常会使用 RPC 协议通信;
对于外部系统,为了提供更标准、更通用且易于明确的接口调用方式,每每会使用与编程语言无关的 HTTP 协议举行通信。
两种方式在代码实现上差别较大,若希望同时实现内部 RPC 通信,外部 HTTP 访问,则需要维护两套服务及接口实现代码,后期代码维护与升级的成本增加,也容易导致错误。
gRPC-Gateway 正是通过将 HTTP 请求转换为 gRPC 请求,并同一通过 gRPC 接口实现所有功能,办理了这一问题。
如何使用 grpc-gateway

首先需要确保系统已经安装了 protoc 工具,使用 gprc-gateway 还需要安装以下两个插件:

  • protoc-gen-grpc-gateway:为 gRPC 服务生成 HTTP/REST API 反向署理代码,从而实现对 gRPC 服务的 HTTP 映射支持;
  • protoc-gen-openapiv2:用于从 Protobuf 描述中生成 OpenAPI v2(Swagger)定义文件。
安装命令:
  1. $ go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2.24.0
  2. $ go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@v2.24.0
复制代码
一个例子:
  1. // MiniBlog 定义了一个 MiniBlog RPC 服务
  2. service MiniBlog {
  3.     // UpdatePost 更新文章
  4.     rpc UpdatePost(UpdatePostRequest) returns (UpdatePostResponse) {
  5.         // 将 UpdatePost 映射为 HTTP PUT 请求,并通过 URL /v1/posts/{postID} 访问
  6.         // {postID} 是一个路径参数,grpc-gateway 会根据 postID 名称,将其解析并映射到
  7.         // UpdatePostRequest 类型中相应的字段.
  8.         // body: "*" 表示请求体中的所有字段都会映射到 UpdatePostRequest 类型。
  9.         option (google.api.http) = {
  10.             put: "/v1/posts/{postID}",
  11.             body: "*",
  12.         };
  13.         // 提供用于生成 OpenAPI 文档的注解
  14.         option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
  15.             // 在文档中简要描述此操作的功能:更新文章。
  16.             summary: "更新文章";
  17.             // 为此操作指定唯一标识符(UpdatePost),便于跟踪
  18.             operation_id: "UpdatePost";
  19.             // 将此操作归类到 "博客管理" 标签组,方便在 OpenAPI 文档中组织接口分组
  20.             tags: "博客管理";
  21.         };
  22.     }
  23. }
  24. // UpdatePostRequest 表示更新文章请求
  25. message UpdatePostRequest {
  26.     // postID 表示要更新的文章 ID,对应 {postID}
  27.     string postID = 1;
  28.     // title 表示更新后的博客标题
  29.     optional string title = 2;
  30.     // content 表示更新后的博客内容
  31.     optional string content = 3;
  32. }
  33. // UpdatePostResponse 表示更新文章响应
  34. message UpdatePostResponse {
  35. }
复制代码
在通过 google.api.http 注解将 gRPC 方法映射为 HTTP 请求时,有以下规则需要服从:

  • HTTP 路径可以包含一个或多个 gRPC 请求消息中的字段,但这些字段应该是 nonrepeated 的原始范例字段;
  • 假如没有 HTTP 请求体,那么出现在请求消息中但没有出现在 HTTP 路径中的字段,将自动成为 HTTP 查询参数;
  • 映射为 URL 查询参数的字段应该是原始范例、repeated 原始范例或 nonrepeated 消息范例;
  • 对于查询参数的 repeated 字段,参数可以在 URL 中重复,形式为 …?param=A&m=B;
  • 对于查询参数中的消息范例,消息的每个字段都会映射为单独的参数,比如 …?foo.a=A&foo.b=B&foo.c=C。
miniblog 实现反向署理服务器


  • 给 gRPC 服务添加 HTTP 映射规则;
  • 生成反向署理代码;
  • 实现反向署理服务器;
  • HTTP 请求测试。
步骤 1 如上节案例,步骤 2 生成反向署理代码:
  1. protoc: # 编译 protobuf 文件.
  2.     @echo "===========> Generate protobuf files"
  3.     @protoc                                              \
  4.         …
  5.         --grpc-gateway_out=allow_delete_body=true,paths=source_relative:$(APIROOT) \
  6.         --openapiv2_out=$(PROJ_ROOT_DIR)/api/openapi \
  7.         --openapiv2_opt=allow_delete_body=true,logtostderr=true \
  8.         $(shell find $(APIROOT) -name *.proto)
复制代码
make protoc
步骤 3 实现反向署理服务器:
  1. func (s *UnionServer) Run() error {
  2.         log.Infow("Start to listening the incoming requests on grpc address", "addr", s.cfg.GRPCOptions.Addr)
  3.         // 启动 gRPC 服务器
  4.         go func() {
  5.                 log.Infow("Starting gRPC server", "addr", s.cfg.GRPCOptions.Addr)
  6.                 if err := s.srv.Serve(s.lis); err != nil {
  7.                         log.Fatalw("Failed to serve", "err", err)
  8.                 }
  9.         }()
  10.         // 等待 gRPC 服务器启动
  11.         time.Sleep(time.Second)
  12.         dialOptions := []grpc.DialOption{
  13.                 grpc.WithBlock(),
  14.                 grpc.WithTransportCredentials(insecure.NewCredentials()),
  15.         }
  16.         ctx := context.Background()
  17.         conn, err := grpc.Dial(s.cfg.GRPCOptions.Addr, dialOptions...)
  18.         if err != nil {
  19.                 return err
  20.         }
  21.         defer conn.Close()
  22.         gwmux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{
  23.                 MarshalOptions: protojson.MarshalOptions{
  24.                         // 设置序列化 protobuf 数据时,枚举类型的字段以数字格式输出.
  25.                         // 否则,默认会以字符串格式输出,跟枚举类型定义不一致,带来理解成本.
  26.                         UseEnumNumbers: true,
  27.                 },
  28.         }))
  29.         // 该方法会注册 HTTP 路由,并将 HTTP 请求转换为 gRPC 请求,发送到 gRPC 客户端 conn 中
  30.         if err := apiv1.RegisterMiniBlogHandler(ctx, gwmux, conn); err != nil {
  31.                 return err
  32.         }
  33.         log.Infow("Start to listening the incoming requests", "protocol", "http", "addr", s.cfg.HTTPOptions.Addr)
  34.         httpsrv := &http.Server{
  35.                 Addr:    s.cfg.HTTPOptions.Addr,
  36.                 Handler: gwmux,
  37.         }
  38.         if err := httpsrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
  39.                 return err
  40.         }
  41.         return nil
  42. }
复制代码

  • insecure.NewCredentials() 是 gRPC 提供的一个函数,用于创建不安全的传输凭据(TransportCredentials)。使用这种凭据时,gRPC 客户端和服务端之间的通信不会加密,也不会举行任何身份验证。因为 HTTP 请求转发到 gRPC 客户端,是内部转发行为,所以这里不消举行通信加密和身份验证。






课程条记

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

没腿的鸟

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表