咱们都知道 gRPC
并非万能的工具。 在某些状况下,咱们仍然想提供传统的 HTTP/JSON API
。缘由可能从保持向后兼容性到支持编程语言或 gRPC
没法很好地支持的客户端。可是仅仅为了公开 HTTP/JSON API
而编写另外一个服务是一项很是耗时且乏味的任务。git
那么,有什么方法能够只编写一次代码,却能够同时在 gRPC
和 HTTP/JSON
中提供 API
?github
答案是 Yes
。golang
gRPC-Gateway
是 Google protocol buffers compiler protoc
的插件。 它读取 protobuf service
定义并生成反向代理服务器( reverse-proxy server
) ,该服务器将 RESTful HTTP API
转换为 gRPC
。 该服务器是根据服务定义中的 google.api.http
批注(annotations
)生成的。编程
这有助于你同时提供 gRPC
和 HTTP/JSON
格式的 API
。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
中可用。curl
咱们将在本教程的新模块中进行工做,所以,请当即在您选择的文件夹中建立该模块:tcp
建立 go.mod 文件
使用 go mod init
命令启动你的 module
以建立 go.mod
文件。
运行 go mod init
命令,给它代码所在 module
的路径。在这里,使用 github.com/myuser/myrepo
做为 module
路径—在生产代码中,这将是能够从其中下载 module
的 URL
。
$ 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-Gateway
,咱们首先要制做一个 hello world gRPC
服务。
在建立 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
是一个工具,它提供了各类 protobuf
实用程序,如 linting
, breaking change detection
和 generation
。请在 https://docs.buf.build/installation/
上找到安装说明。
它是经过 buf.yaml
文件配置的,应将其检入你存储库的根目录中。 若是存在,Buf
将自动读取此文件。 也能够经过命令行标志 --config
提供配置,该标志接受 .json
或 .yaml
文件的路径,或是直接 JSON
或 YAML
数据。
全部使用本地 .proto
文件做为输入的 Buf
操做都依赖于有效的构建配置。这个配置告诉 Buf
在哪里搜索 .proto
文件,以及如何处理导入。与 protoc
(全部 .proto
文件都是在命令行上手动指定的)不一样,buf
的操做方式是递归地发现配置下的全部 .proto
文件并构建它们。
下面是一个有效配置的示例,假设您的 .proto
文件根位于相对于存储库根的 proto
文件夹中。
version: v1beta1 name: buf.build/myuser/myrepo build: roots: - proto
要为 Go
生成 type
和 gRPC stubs
,请在存储库的根目录下建立文件 buf.gen.yaml
:
version: v1beta1 plugins: - name: go out: proto opt: paths=source_relative - name: go-grpc out: proto opt: paths=source_relative
咱们使用 go
和 go-grpc
插件生成 Go types
和 gRPC service
定义。咱们正在输出相对于 proto
文件夹的生成文件,并使用 path=source_relative
选项,这意味着生成的文件将与源 .proto
文件显示在同一目录中。
而后运行:
$ buf generate
这将为咱们的 proto
文件层次结构中的每一个 protobuf
软件包生成一个 *.pb.go
和 *_grpc.pb.go
文件。
这是一个 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
咱们使用 go
和 go-grpc
插件生成 Go types
和 gRPC service
定义。咱们正在输出相对于 proto
文件夹的生成文件,并使用 path=source_relative
选项,这意味着生成的文件将与源 .proto
文件显示在同一目录中。
这将为 proto/helloworld/hello_world.proto
生成一个 *.pb.go
和 *_grpc.pb.go
文件。
在建立 main.go
文件以前,咱们假设用户已经建立了一个名为 github.com/myuser/myrepo
的 go.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)) }
如今,咱们已经能够使用 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
批注添加到了 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()) }
如今咱们能够启动服务器了:
$ go run main.go
而后,咱们使用 cURL
发送 HTTP
请求:
$ curl -X POST -k http://localhost:8090/v1/example/echo -d '{"name": " hello"}'
{"message":"hello world"}
我是为少 微信:uuhells123 公众号:黑客下午茶 加我微信(互相学习交流),关注公众号(获取更多学习资料~)