gRPC 同时提供 Restful API 接口

编辑于 2022-10-24 20:27 阅读 860

gRPC支持很多语言,但是种种原因,要么对方的语言不支持,要么老项目无法改造,这时就需要提供Restful API 接口。如果重新写一套肯定是不划算的,这时候使用gRPC-Gateway,只需要在现有的gRPC项目做稍许修改就可以轻松实现Restful API 接口

引用etcd文档中的一段话

为什么你应该考虑使用gRPC网关? etcd v3使用gRPC作为其消息传递协议。etcd项目包括一个基于gRPC的Go客户端和一个命令行实用程序etcdctl,用于通过gRPC与etcd集群通信。对于不支持gRPC的语言,etcd提供JSON gRPC网关。此网关服务于RESTful代理,将HTTP/JSON请求转换为gRPC消息。

安装

编译

首先项目启用了Go Modules,随便新建一个文件,比如tmp.go,内容如下

// +build tools

package tools

import (
    _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
    _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
)

然后运行go mod tidy来解析依赖。再通过运行以下命令来安装

go install \
    github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
    github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2

这时,你应该可以看到$GOBIN目录多了以下两个文件

protoc-gen-grpc-gateway
protoc-gen-openapiv2

这里默认你已经提前装好了protoc-gen-go-grpcprotoc-gen-go

最后,上面创建的tmp.go文件就可以删除了

下载二进制文件

https://github.com/grpc-ecosystem/grpc-gateway/releases

生成代码

protoc

需要从 googleapis存储库 复制几个文件

google/api/annotations.proto
google/api/field_behavior.proto
google/api/http.proto
google/api/httpbody.proto

本地目录如下

cuiwei@weideMacBook-Pro grpc-demo % tree protobuf 
protobuf
├── google
│   └── api
│       ├── annotations.proto
│       ├── field_behavior.proto
│       ├── http.proto
│       └── httpbody.proto
└── helloworld.proto

然后修改helloworld.proto

 syntax = "proto3";
 package your.service.v1;
 option go_package = "github.com/yourorg/yourprotos/gen/go/your/service/v1";
+
+import "google/api/annotations.proto";
+
 message StringMessage {
   string value = 1;
 }

 service YourService {
-  rpc Echo(StringMessage) returns (StringMessage) {}
+  rpc Echo(StringMessage) returns (StringMessage) {
+    option (google.api.http) = {
+      post: "/v1/example/echo"
+      body: "*"
+    };
+  }
 }

最后生成代码

cuiwei@weideMacBook-Pro protobuf % protoc --go_out=. ./helloworld.proto 

cuiwei@weideMacBook-Pro protobuf % protoc --go-grpc_out=require_unimplemented_servers=false:. ./helloworld.proto 

cuiwei@weideMacBook-Pro protobuf % protoc -I . --grpc-gateway_out ../gen \
--grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt paths=source_relative \
--grpc-gateway_opt generate_unbound_methods=true \
helloworld.proto

buf

安装请移步:https://www.cuiwei.net/p/1679512807

gw_mapping.yaml

type: google.api.Service
config_version: 3

http:
  rules:
    - selector: blog.v1.BlogService.AdminDetail
      get: "/v1/admin/{admin_id}"
    - selector: blog.v1.BlogService.AdminChange
      put: "/v1/admin/{admin_id}"
    - selector: blog.v1.BlogService.AdminChangepasswd
      put: "/v1/admin/{admin_id}/password"
    - selector: blog.v1.BlogService.AdminAvatar
      put: "/v1/admin/{admin_id}/avatar"
    - selector: blog.v1.BlogService.AdminLogout
      delete: "/v1/admin/{admin_id}/logout"

buf.gen.yaml

version: v1
managed:
  enabled: true
  go_package_prefix:
    default: ent/gen/proto/go

plugins:
  - name: grpc-gateway
    out: gen/proto/go
    opt:
      - paths=source_relative
      - grpc_api_configuration=blogapis/blog/v1/gw_mapping.yaml
      - allow_repeated_fields_in_body=true
      - generate_unbound_methods=true
  - name: openapiv2
    out: gen/proto/go
    opt:
      - grpc_api_configuration=blogapis/blog/v1/gw_mapping.yaml
      - allow_repeated_fields_in_body=true

创建入口文件

这里命名httpserver.go

package main

import (
  "context"
  "flag"
  "net/http"

  "github.com/golang/glog"
  "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/insecure"

  gw "github.com/yourorg/yourrepo/proto/gen/go/your/service/v1/your_service"  // Update
)

var (
  grpcServerEndpoint = flag.String("grpc-server-endpoint",  "localhost:9090", "gRPC server endpoint")
)

func run() error {
  ctx := context.Background()
  ctx, cancel := context.WithCancel(ctx)
  defer cancel()

  // Register gRPC server endpoint
  // Note: Make sure the gRPC server is running properly and accessible
  mux := runtime.NewServeMux()
  opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
  err := gw.RegisterYourServiceHandlerFromEndpoint(ctx, mux,  *grpcServerEndpoint, opts)
  if err != nil {
    return err
  }

  // Start HTTP server (and proxy calls to gRPC server endpoint)
  return http.ListenAndServe(":8081", mux)
}

func main() {
  flag.Parse()
  defer glog.Flush()

  if err := run(); err != nil {
    glog.Fatal(err)
  }
}

以上代码需要根据实际情况做些修改

最后,执行go run httpserver.go,没意外的话服务就起来了,然后以post方式请求http://localhost:8081/v1/example/echo就能看到结果了

参考

https://github.com/grpc-ecosystem/grpc-gateway

https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/examples/

广而告之,我的新作品《语音助手》上架Google Play了,欢迎下载体验