gRPC-Gateway 简介

我们都知道 gRPC 并不是万能的工具。 在某些情况下,我们仍然想提供传统的 HTTP/JSON API。原因可能从保持向后兼容性到支持编程语言或 gRPC 无法很好地支持的客户端。但是仅仅为了公开 HTTP/JSON API 而编写另一个服务是一项非常耗时且乏味的任务。

那么,有什么方法可以只编写一次代码,却可以同时在 gRPCHTTP/JSON 中提供 API

答案是 Yes

gRPC-GatewayGoogle protocol buffers compiler protoc 的插件。 它读取 protobuf service 定义并生成反向代理服务器( reverse-proxy server) ,该服务器将 RESTful HTTP API 转换为 gRPC。 该服务器是根据服务定义中的 google.api.http 批注(annotations)生成的。

这有助于你同时提供 gRPCHTTP/JSON 格式的 API

开始之前

在开始编码之前,我们必须安装一些工具。

在示例中,我们将使用 Go gRPC Server,因此请首先从 https://golang.org/dl/ 安装 Go

安装 Go 之后,请使用 go get 下载以下软件包:

$ go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
$ go get google.golang.org/protobuf/cmd/protoc-gen-go
$ go get google.golang.org/grpc/cmd/protoc-gen-go-grpc

这将安装我们生成存根所需的协议生成器插件。确保将 $GOPATH/bin 添加到 $PATH 中,以便通过 go get 安装的可执行文件在 $PATH 中可用。

我们将在本教程的新模块中进行工作,因此,请立即在您选择的文件夹中创建该模块:

创建 go.mod 文件

使用 go mod init 命令启动你的 module 以创建 go.mod 文件。

运行 go mod init 命令,给它代码所在 module 的路径。在这里,使用 github.com/myuser/myrepo 作为 module 路径—在生产代码中,这将是可以从其中下载 moduleURL

$ go mod init github.com/myuser/myrepo
go: creating new go.mod: module github.com/myuser/myrepo

go mod init 命令创建一个 go.mod 文件,该文件将您的代码标识为可以从其他代码中使用的 module。 您刚创建的文件仅包含模块名称和代码支持的 Go 版本。 但是,当您添加依赖项(即其他模块的软件包)时,go.mod 文件将列出要使用的特定 module 版本。 这样可以使构建具有可复制性,并使您可以直接控制要使用的 module 版本。

用 gRPC 创建一个简单的 hello world

为了了解 gRPC-Gateway,我们首先要制作一个 hello world gRPC 服务。

使用 protocol buffers 定义 gRPC service

在创建 gRPC 服务之前,我们应该创建一个 proto 文件来定义我们需要的东西,这里我们在 proto/helloworld/ 目录下创建了一个名为 hello_world.proto 的文件。

gRPC service 使用 Google Protocol Buffers 定义的。这里定义如下:

syntax = "proto3";

package helloworld;

// The greeting service definition
service Greeter {
// Sends 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 greetings
message HelloReply {
string message = 1;
}

使用 buf 生成 stubs

Buf 是一个工具,它提供了各种 protobuf 实用程序,如 linting, breaking change detectiongeneration。请在 https://docs.buf.build/installation/ 上找到安装说明。

它是通过 buf.yaml 文件配置的,应将其检入你存储库的根目录中。 如果存在,Buf 将自动读取此文件。 也可以通过命令行标志 --config 提供配置,该标志接受 .json.yaml 文件的路径,或是直接 JSONYAML 数据。

所有使用本地 .proto 文件作为输入的 Buf 操作都依赖于有效的构建配置。这个配置告诉 Buf 在哪里搜索 .proto 文件,以及如何处理导入。与 protoc(所有 .proto 文件都是在命令行上手动指定的)不同,buf 的操作方式是递归地发现配置下的所有 .proto 文件并构建它们。

下面是一个有效配置的示例,假设您的 .proto 文件根位于相对于存储库根的 proto 文件夹中。

version: v1beta1
name: buf.build/myuser/myrepo
build:
roots:
- proto

要为 Go 生成 typegRPC stubs,请在存储库的根目录下创建文件 buf.gen.yaml

version: v1beta1
plugins:
- name: go
out: proto
opt: paths=source_relative
- name: go-grpc
out: proto
opt: paths=source_relative

我们使用 gogo-grpc 插件生成 Go typesgRPC service 定义。我们正在输出相对于 proto 文件夹的生成文件,并使用 path=source_relative 选项,这意味着生成的文件将与源 .proto 文件显示在同一目录中。

然后运行:

$ buf generate

这将为我们的 proto 文件层次结构中的每个 protobuf 软件包生成一个 *.pb.go*_grpc.pb.go 文件。

使用 protoc 生成 stubs

这是一个 protoc 命令可能会生成 Go stubs 的示例,假设您位于存储库的根目录,并且您的 proto 文件位于一个名为 proto 的目录中:

$ protoc -I ./proto \
--go_out ./proto --go_opt paths=source_relative \
--go-grpc_out ./proto --go-grpc_opt paths=source_relative \
./proto/helloworld/hello_world.proto

我们使用 gogo-grpc 插件生成 Go typesgRPC service 定义。我们正在输出相对于 proto 文件夹的生成文件,并使用 path=source_relative 选项,这意味着生成的文件将与源 .proto 文件显示在同一目录中。

这将为 proto/helloworld/hello_world.proto 生成一个 *.pb.go*_grpc.pb.go 文件。

创建 main.go

在创建 main.go 文件之前,我们假设用户已经创建了一个名为 github.com/myuser/myrepogo.mod。此处的 import 使用的是相对于存储库根目录的 proto/helloworld 中生成的文件的路径。

package main

import (
"context"
"log"
"net" "google.golang.org/grpc" helloworldpb "github.com/myuser/myrepo/proto/helloworld"
) type server struct{} func NewServer() *server {
return &server{}
} func (s *server) SayHello(ctx context.Context, in *helloworldpb.HelloRequest) (*helloworldpb.HelloReply, error) {
return &helloworldpb.HelloReply{Message: in.Name + " world"}, nil
} func main() {
// Create a listener on TCP port
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalln("Failed to listen:", err)
} // Create a gRPC server object
s := grpc.NewServer()
// Attach the Greeter service to the server
helloworldpb.RegisterGreeterServer(s, &server{})
// Serve gRPC Server
log.Println("Serving gRPC on 0.0.0.0:8080")
log.Fatal(s.Serve(lis))
}

将 gRPC-Gateway 批注添加到现有的 proto 文件中

现在,我们已经可以使用 Go gRPC 服务器,我们需要添加 gRPC-Gateway 批注。

批注定义了 gRPC 服务如何映射到 JSON 请求和响应。 使用 protocol buffers 时,每个 RPC 必须使用 google.api.http 批注定义 HTTP 方法和路径。

因此,我们需要将 google/api/http.proto 导入添加到 proto 文件中。我们还需要添加所需的 HTTP->gRPC 映射。在这种情况下,我们会将 POST /v1/example/echo 映射到我们的 SayHello RPC

syntax = "proto3";

package helloworld;

import "google/api/annotations.proto";

// Here is the overall greeting service definition where we define all our endpoints
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/example/echo"
body: "*"
};
}
} // The request message containing the user's name
message HelloRequest {
string name = 1;
} // The response message containing the greetings
message HelloReply {
string message = 1;
}

生成 gRPC-Gateway stubs

现在我们已经将 gRPC-Gateway 批注添加到了 proto 文件中,我们需要使用 gRPC-Gateway 生成器来生成存根(stubs)。

使用 buf

我们需要将 gRPC-Gateway 生成器添加到生成配置中:

version: v1beta1
plugins:
- name: go
out: proto
opt: paths=source_relative
- name: go-grpc
out: proto
opt: paths=source_relative,require_unimplemented_servers=false
- name: grpc-gateway
out: proto
opt: paths=source_relative

我们还需要将 googleapis 依赖项添加到我们的 buf.yaml 文件中:

version: v1beta1
name: buf.build/myuser/myrepo
deps:
- buf.build/beta/googleapis
build:
roots:
- proto

然后,我们需要运行 buf beta mod update 以选择要使用的依赖项版本。

就是这样!现在,如果您运行:

$ buf generate

它应该产生一个 *.gw.pb.go 文件。

使用 protoc

在使用 protoc 生成 stubs 之前,我们需要将一些依赖项复制到我们的 proto 文件结构中。将一部分 googleapis 从官方存储库复制到您本地的原始文件结构中。之后看起来应该像这样:

proto
├── google
│ └── api
│ ├── annotations.proto
│ └── http.proto
└── helloworld
└── hello_world.proto

现在我们需要将 gRPC-Gateway 生成器添加到 protoc 调用中:

$ protoc -I ./proto \
--go_out ./proto --go_opt paths=source_relative \
--go-grpc_out ./proto --go-grpc_opt paths=source_relative \
--grpc-gateway_out ./proto --grpc-gateway_opt paths=source_relative \
./proto/helloworld/hello_world.proto

这将生成一个 *.gw.pb.go 文件。

我们还需要在 main.go 文件中添加 gRPC-Gateway 多路复用器(mux)并为其提供服务。

package main

import (
"context"
"log"
"net"
"net/http" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc" helloworldpb "github.com/myuser/myrepo/proto/helloworld"
) type server struct{
helloworldpb.UnimplementedGreeterServer
} func NewServer() *server {
return &server{}
} func (s *server) SayHello(ctx context.Context, in *helloworldpb.HelloRequest) (*helloworldpb.HelloReply, error) {
return &helloworldpb.HelloReply{Message: in.Name + " world"}, nil
} func main() {
// Create a listener on TCP port
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalln("Failed to listen:", err)
} // Create a gRPC server object
s := grpc.NewServer()
// Attach the Greeter service to the server
helloworldpb.RegisterGreeterServer(s, &server{})
// Serve gRPC server
log.Println("Serving gRPC on 0.0.0.0:8080")
go func() {
log.Fatalln(s.Serve(lis))
}() // Create a client connection to the gRPC server we just started
// This is where the gRPC-Gateway proxies the requests
conn, err := grpc.DialContext(
context.Background(),
"0.0.0.0:8080",
grpc.WithBlock(),
grpc.WithInsecure(),
)
if err != nil {
log.Fatalln("Failed to dial server:", err)
} gwmux := runtime.NewServeMux()
// Register Greeter
err = helloworldpb.RegisterGreeterHandler(context.Background(), gwmux, conn)
if err != nil {
log.Fatalln("Failed to register gateway:", err)
} gwServer := &http.Server{
Addr: ":8090",
Handler: gwmux,
} log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
log.Fatalln(gwServer.ListenAndServe())
}

测试 gRPC-Gateway

现在我们可以启动服务器了:

$ go run main.go

然后,我们使用 cURL 发送 HTTP 请求:

$ curl -X POST -k http://localhost:8090/v1/example/echo -d '{"name": " hello"}'
{"message":"hello world"}

Refs

我是为少
微信:uuhells123
公众号:黑客下午茶
加我微信(互相学习交流),关注公众号(获取更多学习资料~)

云原生 API 网关,gRPC-Gateway V2 初探的更多相关文章

  1. 云原生之旅 - 9)云原生时代网关的后起之秀Envoy Proxy 和基于Envoy 的 Emissary Ingress

    前言 前一篇文章讲述了基于Nginx代理的Kuberenetes Ingress Nginx[云原生时代的网关 Ingress Nginx]这次给大家介绍下基于Envoy的 Emissary Ingr ...

  2. API网关【gateway 】- 3

    最近在公司进行API网关重写,公司内采用serverMesh进行服务注册,调用,这里结合之前学习对API网关服务进行简单的总结与分析. 由于采用了大量的nginx相关的东西,所以在此记录一下: 在ng ...

  3. API网关【gateway 】- 2

    最近在公司进行API网关重写,公司内采用serverMesh进行服务注册,调用,这里结合之前学习对API网关服务进行简单的总结与分析. 由于采用了大量的nginx相关的东西,所以在此记录一下: 配置连 ...

  4. API网关【gateway 】- 1

    最近在公司进行API网关重写,公司内采用serverMesh进行服务注册,调用,这里结合之前学习对API网关服务进行简单的总结与分析. 网关的单节点场景: 网关的多节点场景: 这里的多节点是根据模块进 ...

  5. 微服务架构学习与思考(10):微服务网关和开源 API 网关01-以 Nginx 为基础的 API 网关详细介绍

    微服务架构学习与思考(10):微服务网关和开源 API 网关01-以 Nginx 为基础的 API 网关详细介绍 一.为什么会有 API Gateway 网关 随着微服务架构的流行,很多公司把原有的单 ...

  6. API 管理在云原生场景下的机遇与挑战

    作者 | 张添翼 来源 | 尔达Erda公众号 ​ 云原生下的机遇和挑战 标准和生态的意义 自从 Kubernetes v1.0 于 2015 年 7 月 21 日发布,CNCF 组织随后建立以来,其 ...

  7. 借助腾讯云的云函数实现一个极简的API网关

    借助腾讯云的云函数实现一个极简的API网关 Intro 微信小程序的域名需要备案,但是没有大陆的服务器,而且觉得备案有些繁琐,起初做的小程序都有点想要放弃了,后来了解到腾讯云的云函数,于是利用腾讯云的 ...

  8. SpringCloud实战 | 第四篇:SpringCloud整合Gateway实现API网关

    一. 前言 微服务实战系列是基于开源微服务项目 有来商城youlai-mall 版本升级为背景来开展的,本篇则是讲述API网关使用Gateway替代Zuul,有兴趣的朋友可以进去给个star,非常感谢 ...

  9. API 网关的选型和持续集成

    2019 年 8 月 31 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙·成都站,APISIX 作者温铭在活动上做了< API 网关的选 ...

随机推荐

  1. Android APP 多端适配

    Android APP 多端适配 传统的多终端适配方案,是为大尺寸 Pad开发一个特定的 HD版本. 但是目前支持 Android 系统的设备类型越来越丰富,不同类型的设备尺寸也越来越多样化,特定的H ...

  2. window resize & resize observer

    window resize & resize observer https://developer.mozilla.org/en-US/docs/Web/API/Window/resize_e ...

  3. 在.NET Core 中使用 FluentValidation 进行规则验证

    不用说,规则验证很重要,无效的参数,可能会导致程序的异常. 如果使用Web API或MVC页面,那么可能习惯了自带的规则验证,我们的控制器很干净: public class User { [Requi ...

  4. epoll原理详解及epoll反应堆模型

    本文转载自epoll原理详解及epoll反应堆模型 导语 设想一个场景:有100万用户同时与一个进程保持着TCP连接,而每一时刻只有几十个或几百个TCP连接是活跃的(接收TCP包),也就是说在每一时刻 ...

  5. Github Packages和Github Actions实践之CI/CD

    概述 Github在被微软收购后,不忘初心,且更大力度的造福开发者们,推出了免费私有仓库等大更新.近期又开放了packages和actions两个大招,经笔者试用后感觉这两个功能配合起来简直无敌. G ...

  6. 开启算法之路,还原题目,用debug调试搞懂每一道题

    文章简述 大家好,本篇是个人的第 3 篇文章. 承接第一篇文章<手写单链表基础之增,删,查!附赠一道链表题>,在第一篇文章中提过,在刷算法题之前先将基础知识过一遍,这样对后面的做算法题是很 ...

  7. SpringCloud(四):服务注册中心Eureka Eureka高可用集群搭建 Eureka自我保护机制

    第四章:服务注册中心 Eureka 4-1. Eureka 注册中心高可用集群概述在微服务架构的这种分布式系统中,我们要充分考虑各个微服务组件的高可用性 问题,不能有单点故障,由于注册中心 eurek ...

  8. Layui 源码浅读(模块加载原理)

    经典开场 // Layui ;! function (win) { var Lay = function () { this.v = '2.5.5'; }; win.layui = new Lay() ...

  9. 力扣496. 下一个更大元素 I

    原题 1 class Solution: 2 def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[i ...

  10. CentOS rpm常用功能记录

    CentOS7主要有rpm和yum这两种包软件的管理.两者有功能上的区别,其中主要区别是:yum使用简单但需要联网,yum会去网上包源去获取所需要的软件包.而rpm的需要做的事情就更细一些,比如我们需 ...