本文最后更新于:10 个月前
前言
示例代码已经上传到github:点击跳转 gRPC官方文档:点击跳转 grpc-gateway官方文档:点击跳转 源自 coreos 的一篇博客,转载到了 gRPC 官方博客 gRPC with REST and Open APIs 。
一、gRPC-Gateway概述 1、简述
取自官方概述: grpc-gateway is a plugin of protoc. It reads gRPC service definition, and generates a reverse-proxy server which translates a RESTful JSON API into gRPC. This server is generated according to custom options in your gRPC definition.
gRPC-Gateway 是 protoc 的插件
。它读取gRPC服务定义并生成反向代理服务器,将 RESTful JSON API 转换为 gRPC
。该服务器是根据服务定义中的 google.api.http
注释生成的。
2、出现 etcd v3 改用 gRPC 后为了兼容原来的 API,同时要提供 HTTP/JSON 方式的API,为了满足这个需求,要么开发两套 API,要么实现一种转换机制,所以grpc-gateway
诞生了。
通过protobuf的自定义option实现了一个网关
,服务端同时开启gRPC和HTTP服务。
HTTP服务接收客户端请求后转换为grpc请求数据,获取响应后转为json数据返回给客户端。
当 HTTP 请求到达 gRPC-Gateway 时,它将 JSON 数据解析为 Protobuf 消息。使用解析的 Protobuf 消息发出正常的 Go gRPC 客户端请求。
Go gRPC 客户端将 Protobuf 结构编码为 Protobuf 二进制格式
,然后将其发送到 gRPC 服务器。
gRPC 服务器处理请求并以 Protobuf 二进制格式返回响应。
Go gRPC 客户端将其解析为 Protobuf 消息,并将其返回到 gRPC-Gateway,后者将 Protobuf 消息编码为 JSON 并将其返回给原始客户端。
架构如下
二、准备工作 由于本实践偏向 Grpc+Grpc Gateway的方面,我们的需求是同一个服务端支持Rpc和Restful Api,那么就意味着http2、TLS
等等的应用,功能方面就是一个服务端能够接受来自grpc和Restful Api的请求并响应。
本文示例代码已经上传到github:点击跳转
1、目录结构 新建grpc-gateway-example文件夹,我们项目的初始目录目录如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 grpc-gateway-example/ ├── certs ├── client ├── cmd ├── pkg ├── proto │ ├── google │ │ └── api │ │ │ └── annotations.proto │ │ │ └── http.proto │ │ └── protobuf │ │ │ └── descriptor.proto ├── server └── Makefile
certs:存放证书凭证
client:客户端
cmd:存放 cobra 命令模块
pkg:第三方公共模块
proto:protobuf的一些相关文件(含.proto、pb.go、.pb.gw.go),google/api中用于存放annotations.proto、http.proto
、google/protobuf中用于存放descriptor.proto
server:服务端
Makefile:用于存放编译的代码。
2、环境准备 1)Protobuf 详细的请移步到《gRPC(二)入门:Protobuf入门》
2)gRPC 详细的请移步到《gRPC(三)基础:gRPC快速入门》
3)gRPC-Gateway gRPC-Gateway 只是一个插件,只需要安装一下就可以了。这里建议科学上网:
1 go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
3、编写 IDL 1)google.api proto
目录中有 google/api
目录,它用到了 google 官方提供的两个 api 描述文件,主要是针对 grpc-gateway 的 http 转换提供支持,定义了 Protocol Buffer 所扩展的 HTTP Option。
2)hello.proto 编写Demo的 .proto
文件,我们在 proto目录下新建 hello.proto 文件,写入文件内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 syntax = "proto3" ;package proto; option go_package = "./proto/helloworld;helloworld" ;import "proto/google/api/annotations.proto" ; service Hello { rpc SayHello(HelloRequest) returns (HelloResponse) { option (google.api.http) = { post: "/hello_world" body: "*" }; } } message HelloRequest { string referer = 1 ; } message HelloResponse { string message = 1 ; }
在 hello.proto 文件中,引用了 google/api/annotations.proto
,达到支持HTTP Option的效果
定义了一个 serviceRPC 服务 HelloWorld,在其内部定义了一个 HTTP Option
的POST方法,HTTP 响应路径为/hello_world
。
定义message类型HelloWorldRequest、HelloWorldResponse
,用于响应请求和返回结果。
每个方法都必须添加 google.api.http
注解后 gRPC-Gateway 才能生成对应 http 方法。 其中post为 HTTP Method,即 POST 方法,/hello_world
则是请求路径。
3)编译proto 在Makefile文件内输入以下内容:
1 2 protoc: protoc --go_out=. --go -grpc_out=. --grpc-gateway_out=. ./proto
Go Plugins 用于生成 .pb.go 文件
gRPC Plugins 用于生成 _grpc.pb.go
gRPC-Gateway 则是 pb.gw.go
使用 make protoc
编译proto:
1 2 ➜ make protoc protoc --go_out=. --go -grpc_out=. --grpc-gateway_out=. ./proto
4、制作证书 详细的请移步到《gRPC(五)进阶:通过TLS建立安全连接》
在服务端支持Rpc和Restful Api,需要用到TLS,因此我们要先制作证书
进入certs目录,生成TLS所需的公钥密钥文件
1)生成CA根证书 在 ca.conf
文件并写入内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [ req ] default_bits = 4096 distinguished_name = req_distinguished_name [ req_distinguished_name ] countryName = GB countryName_default = CN stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = ZheJiang localityName = Locality Name (eg, city) localityName_default = HuZhou organizationName = Organization Name (eg, company) organizationName_default = Step commonName = linzyblog.netlify.app commonName_max = 64 commonName_default = linzyblog.netlify.app
生成ca私钥,得到ca.key
1 openssl genrsa -out ca.key 4096
生成ca证书签发请求,得到ca.csr
1 2 3 4 5 6 $ openssl req -new -sha256 -out ca.csr -key ca.key -config ca.conf GB [CN]: State or Province Name (full name) [ZheJiang]: Locality Name (eg, city) [HuZhou]: Organization Name (eg, company) [Step]: linzyblog.netlify.app [linzyblog.netlify.app]:
生成ca根证书,得到ca.crt
1 openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.crt
2)生成终端用户证书 在 server.conf
写入以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [ req ] default_bits = 2048 distinguished_name = req_distinguished_name [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CN stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = ZheJiang localityName = Locality Name (eg, city) localityName_default = HuZhou organizationName = Organization Name (eg, company) organizationName_default = Step commonName = CommonName (e.g. server FQDN or YOUR name) commonName_max = 64 commonName_default = linzyblog.netlify.app [ req_ext ] subjectAltName = @alt_names [alt_names] DNS.1 = grpc-gateway-example IP = 127.0 .0 .1
生成私钥,得到server.key
1 openssl genrsa -out server.key 2048
生成证书签发请求,得到server.csr
1 openssl req -new -sha256 -out server.csr -key server.key -config server.conf
这里也一直回车就好。
用CA证书生成终端用户证书,得到server.crt
1 openssl x509 -req -days 3650 -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.pem -extensions req_ext -extfile server.conf
这样我们需要的证书凭证就足够了
三、命令行模块 cmd 1、Cobra介绍 官方文档:点击跳转
Cobra 是一个用于创建强大的现代 CLI 应用程序的库
。它提供了一个简单的界面来创建强大的现代 CLI 界面,类似于 git 和 go 工具。
Cobra 提供:
简易的子命令行模式
完全兼容 POSIX
的命令行模式(包括短版和长版)
嵌套的子命令
全局、本地和级联flags
使用Cobra很容易的生成应用程序和命令,使用 cobra create appname
和 cobra add cmdname
提供智能提示
自动生成commands和flags的帮助信息
自动生成详细的 help
信息,如 app -help。
自动识别帮助 flag、 -h,--help
。
自动生成应用程序在 bash 下命令自动完成功能。
自动生成应用程序的 man 手册。
命令行别名。
自定义 help
和 usage
信息。
可选的与 viper
的紧密集成。
2、概念 Cobra 建立在命令(commands)、参数(arguments )、选项(flags)的结构之上。
commands :命令代表行为,一般表示 action,即运行的二进制命令服务。同时可以拥有子命令(children commands)
arguments :参数代表命令行参数。
flags :选项代表对命令行为的改变,即命令行选项。二进制命令的配置参数,可对应配置文件。参数可分为全局参数和子命令参数。
最好的命令行程序在实际使用时,就应该像在读一段优美的语句,能够更加直观的知道如何与用户进行交互。
执行命令行程序应该遵循一般的格式:
1 2 3 4 5 6 7 8 docker pull alpine:latest docker ps -a git commit -m "linzy"
3、安装 使用 Cobra 很容易。首先,用于go get安装最新版本的库。
1 go get -u github.com/spf13/cobra@latest
4、编写 server 在编写 cmd 时需要先用 server 进行测试关联,因此这一步我们先写 server.go 用于测试
在 server 模块下 新建 server.go
文件,写入测试内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package serverimport ( "log" )var ( ServerPort string CertName string CertPemPath string CertKeyPath string )func Serve () (err error ) { log.Println(ServerPort) log.Println(CertName) log.Println(CertPemPath) log.Println(CertKeyPath) return nil }
5、编写 cmd 在cmd模块下 新建 root.go
文件,写入内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package cmdimport ( "fmt" "os" "github.com/spf13/cobra" )var rootCmd = &cobra.Command{ Use: "grpc" , Short: "Run the gRPC hello-world server" , }func Execute () { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1 ) } }
当前 cmd 目录下继续 新建 server.go
文件,写入内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package cmdimport ( "github.com/spf13/cobra" "grpc-gateway-example/server" "log" )var serverCmd = &cobra.Command{ Use: "server" , Short: "Run the gRPC hello-world server" , Run: func (cmd *cobra.Command, args []string ) { defer func () { if err := recover (); err != nil { log.Println("Recover error : %v" , err) } }() server.Serve() }, }func init () { serverCmd.Flags().StringVarP(&server.ServerPort, "port" , "p" , "50052" , "server port" ) serverCmd.Flags().StringVarP(&server.CertPemPath, "cert-pem" , "" , "./certs/server.pem" , "cert pem path" ) serverCmd.Flags().StringVarP(&server.CertKeyPath, "cert-key" , "" , "./certs/server.key" , "cert key path" ) serverCmd.Flags().StringVarP(&server.CertName, "cert-name" , "" , "grpc-gateway-example" , "server's hostname" ) rootCmd.AddCommand(serverCmd) }
6、启动 & 请求 我们在 grpc-gateway-example
目录下,新建文件main.go,写入内容:
1 2 3 4 5 6 7 package mainimport "grpc-gateway-example/cmd" func main () { cmd.Execute() }
当前目录下执行·go run main.go server·,查看输出是否为(此时应为默认值):
1 2 3 4 5 $ go run main.go server2022 /11 /10 12 :08 :22 50052 2022 /11 /10 12 :08 :22 grpc-gateway-example 2022 /11 /10 12 :08 :22 ./certs/server.pem 2022 /11 /10 12 :08 :22 ./certs/server.key
执行go run main.go server --port=8000 --cert-pem=test-pem --cert-key=test-key --cert-name=test-name
,检验命令行参数是否正确:
1 2 3 4 5 $ go run main.go server --port=8000 --cert-pem=test-pem --cert-key=test-key --cert-name=test-name2022 /11 /10 12 :24 :53 8000 2022 /11 /10 12 :24 :54 test-name2022 /11 /10 12 :24 :54 test-pem 2022 /11 /10 12 :24 :54 test-key
到这都无误,我们的 cmd
模块编写就正确了,下面开始我们的重点
7、目录结构 完成以上操作之后我们的目录是这样的结构,看看是不是缺少了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 grpc-gateway-example/ ├── certs ├── client ├── cmd │ ├── root.go │ └── server.go ├── pkg ├── proto │ ├── google │ │ └── api │ │ │ ├── annotations.proto │ │ │ └── http.proto │ │ └── protobuf │ │ │ └── descriptor.proto │ ├── helloworld │ │ ├── hello.pb.go │ │ ├── hello.pb.gw.go │ │ └── hello_grpc.pb.go │ ├── hello.proto ├── server │ └── server.go └── Makefile
四、服务端模块 server 1、编写 hello.proto 在server目录下新建文件 hello.go
,写入文件内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package serverimport ( "context" "grpc-gateway-example/proto/helloworld" )type helloService struct { helloworld.UnimplementedHelloServer }func NewHelloService () *helloService { return &helloService{} }func (h helloService) SayHello(ctx context.Context, r *helloworld.HelloRequest) (*helloworld.HelloResponse, error ) { return &helloworld.HelloResponse{ Message: "hello grpc-gateway" , }, nil }
2、编写 grpc.go 在 pkg 下新建 util 目录,新建 grpc.go
文件,写入内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package utilimport ( "google.golang.org/grpc" "net/http" "strings" )func GrpcHandlerFunc (grpcServer *grpc.Server, otherHandler http.Handler) http.Handler { if otherHandler == nil { return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { grpcServer.ServeHTTP(w, r) }) } return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type" ), "application/grpc" ) { grpcServer.ServeHTTP(w, r) } else { otherHandler.ServeHTTP(w, r) } }) }
GrpcHandlerFunc
函数是用于判断请求是来源于 Rpc 客户端还是 Restful Api 的请求,根据不同的请求注册不同的 ServeHTTP 服务;r.ProtoMajor == 2
也代表着请求必须基于HTTP/2
。 简而言之函数将gRPC请求和HTTP请求分别调用不同的handler处理。
如果不需要 TLS 建立安全链接,则可以使用h2c
:
1 2 3 4 5 6 7 8 9 func GrpcHandlerFunc (grpcServer *grpc.Server, otherHandler http.Handler) http.Handler { return h2c.NewHandler(http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type" ), "application/grpc" ) { grpcServer.ServeHTTP(w, r) } else { otherHandler.ServeHTTP(w, r) } }), &http2.Server{}) }
3、编写 tls.go 在pkg下的 util 目录下,新建 tls.go
文件,写入内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package utilimport ( "crypto/tls" "golang.org/x/net/http2" "io/ioutil" "log" )func GetTLSConfig (certPemPath, certKeyPath string ) *tls.Config { var certKeyPair *tls.Certificate cert, _ := ioutil.ReadFile(certPemPath) key, _ := ioutil.ReadFile(certKeyPath) pair, err := tls.X509KeyPair(cert, key) if err != nil { log.Println("TLS KeyPair err: %v\n" , err) } certKeyPair = &pair return &tls.Config{ Certificates: []tls.Certificate{*certKeyPair}, NextProtos: []string {http2.NextProtoTLS}, } }
GetTLSConfig 函数是用于获取TLS配置
,在内部,我们读取了 server.key 和 server.pem 这类证书凭证文件。经过一系列处理获取 tls.Config
作为 HTTP2 的使用参数。
4、重新编写核心文件 server/server.go 修改server目录下的server.go文件,该文件是我们服务里的核心文件,写入内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 package serverimport ( "context" "crypto/tls" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "grpc-gateway-example/pkg/util" "grpc-gateway-example/proto/helloworld" "log" "net" "net/http" )var ( ServerPort string CertName string CertPemPath string CertKeyPath string EndPoint string )func Serve () (err error ) { EndPoint = ":" + ServerPort conn, err := net.Listen("tcp" , EndPoint) if err != nil { log.Printf("TCP Listen err:%v\n" , err) } tlsConfig := util.GetTLSConfig(CertPemPath, CertKeyPath) srv := createInternalServer(conn, tlsConfig) log.Printf("gRPC and https listen on: %s\n" , ServerPort) if err = srv.Serve(tls.NewListener(conn, tlsConfig)); err != nil { log.Printf("ListenAndServe: %v\n" , err) } return err }func createInternalServer (conn net.Listener, tlsConfig *tls.Config) *http.Server { var opts []grpc.ServerOption creds, err := credentials.NewServerTLSFromFile(CertPemPath, CertKeyPath) if err != nil { log.Printf("Failed to create server TLS credentials %v" , err) } opts = append (opts, grpc.Creds(creds)) grpcServer := grpc.NewServer(opts...) helloworld.RegisterHelloServer(grpcServer, NewHelloService()) ctx := context.Background() dcreds, err := credentials.NewClientTLSFromFile(CertPemPath, CertName) if err != nil { log.Printf("Failed to create client TLS credentials %v" , err) } dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)} gwmux := runtime.NewServeMux() if err := helloworld.RegisterHelloHandlerFromEndpoint(ctx, gwmux, EndPoint, dopts); err != nil { log.Printf("Failed to register gw server: %v\n" , err) } mux := http.NewServeMux() mux.Handle("/" , gwmux) return &http.Server{ Addr: EndPoint, Handler: util.GrpcHandlerFunc(grpcServer, mux), TLSConfig: tlsConfig, } }
5、server流程刨析 1)启动监听 net.Listen("tcp", EndPoint)
函数用于监听本地网络地址的监听。其函数原型Listen(ctx context.Context, network, address string) (Listener, error)
参数:
network:必须是tcp, tcp4, tcp6, unix或unixpacket。
address:对于TCP网络,如果address参数中的host为空或未指定的IP地址,则会自动返回一个可用的端口或者IP地址。
net.Listen(“tcp”, EndPoint)函数返回值是Listener
:
1 2 3 4 5 6 7 8 9 10 type Listener interface { Accept() (Conn, error ) Close() error Addr() Addr }
net.Listen
会返回一个监听器的结构体,返回接下来的动作,让其执行下一步的操作,可用执行以下操作Accept、Close、Addr。
2)获取TLSConfig 通过调用 util.GetTLSConfig
函数解析得到 tls.Config
,通过传达给 createInternalServer
函数完成 http.Server 服务的 TLSConfig
配置项使用。
3)创建内部服务 程序采用HTTP2、HTTPS,需要支持TLS,在启动 grpc.NewServer()
前需要将serverOptions
(服务器选项,类似于中间件,可用设置例如凭证、编解码器和保持存活参数等选项。),而前面所获取的 tlsConfig 仅能给HTTP使用,因此第一步我们要创建 grpc 的 TLS 认证凭证。
创建 grpc 的 TLS 认证凭证 引用 google.golang.org/grpc/credentials
第三方包,credentials
包实现gRPC库支持的各种凭据,这些凭据封装了客户机与服务器进行身份验证所需的所有状态,并进行各种断言,例如,关于客户机的身份、角色或是否授权进行特定调用。
我们调用 NewServerTLSFromFile
它能够从服务器的输入证书文件和密钥文件构造TLS凭据。
1 2 3 4 5 6 7 8 9 func NewServerTLSFromFile (certFile, keyFile string ) (TransportCredentials, error ) { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil , err } return NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}}), nil }
grpc ServerOption
grpc.Creds() 其原型为func Creds(c credentials.TransportCredentials) ServerOption
,返回一个为服务器连接设置凭据的ServerOption。
创建 grpc 服务端 grpc.NewServer() 创建一个没有注册服务的grpc服务端,可以配置 ServerOption
注册grpc服务
1 2 helloworld.RegisterHelloServer(grpcServer, NewHelloService())
创建 grpc-gateway
关联组件
1 2 3 4 5 6 7 8 9 10 11 ctx := context.Background() dcreds, err := credentials.NewClientTLSFromFile(CertPemPath, CertName)if err != nil { log.Printf("Failed to create client TLS credentials %v" , err) } dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)}
创建HTTP NewServeMux及注册 grpc-gateway
逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 gwmux := runtime.NewServeMux()if err := helloworld.RegisterHelloHandlerFromEndpoint(ctx, gwmux, EndPoint, dopts); err != nil { log.Printf("Failed to register gw server: %v\n" , err) } mux := http.NewServeMux() mux.Handle("/" , gwmux)
注册具体服务
1 2 3 4 if err := helloworld.RegisterHelloHandlerFromEndpoint(ctx, gwmux, EndPoint, dopts); err != nil { log.Printf("Failed to register gw server: %v\n" , err) }
ctx:上下文
gwmux:grpc-gateway
的请求多路复用器
EndPoint:服务网络地址
dopts:配置好的安全凭据
4)创建Listener 1 2 3 4 5 if err = srv.Serve(tls.NewListener(conn, tlsConfig)); err != nil { log.Printf("ListenAndServe: %v\n" , err) }
5)服务接受请求 我们调用 srv.Serve(tls.NewListener(conn, tlsConfig))
它是http.Server的方法,并且需要一个Listener作为参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func (srv *Server) Serve(l net.Listener) error { ... defer l.Close() ... baseCtx := context.Background() ... ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { rw, e := l.Accept() ... c := srv.newConn(rw) c.setState(c.rwc, StateNew, runHooks) go c.serve(ctx) } }
它创建了一个 context.Background() 上下文对象,并调用 Listener 的 Accept 方法开始接受请求,在获取到连接数据后使用 newConn 创建连接对象,在最后使用goroutine的方式处理连接请求,完成请求后自动关闭连接。
五、验证功能 1、编写 client 在目录client下,创建 main.go 文件,新增以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport ( "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "grpc-gateway-example/proto/helloworld" "log" )func main () { creds, err := credentials.NewClientTLSFromFile("./certs/server.pem" , "grpc-gateway-example" ) if err != nil { log.Println("Failed to create TLS credentials %v" , err) return } conn, err := grpc.Dial(":50052" , grpc.WithTransportCredentials(creds)) defer conn.Close() if err != nil { log.Println(err) } c := helloworld.NewHelloClient(conn) ct := context.Background() body := &helloworld.HelloRequest{ Referer: "Grpc" , } r, err := c.SayHello(ct, body) if err != nil { log.Println(err) } log.Println(r) }
2、启动 & 请求 1 2 3 4 5 6 7 # 启动服务端 $ go run main.go server2022 /11 /10 16 :34 :06 gRPC and https listen on: 50052 # 启动客户端 $ go run client/main.go 2022 /11 /10 16 :34 :43 message:"hello grpc-gateway"
执行测试Restful Api,用POST方式访问https://localhost:50052/hello_world
六、最终目录结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 grpc-gateway-example/ ├── certs │ ├── ca.conf │ ├── ca.crt │ ├── ca.csr │ ├── ca.key │ ├── server.conf │ ├── server.csr │ ├── server.key │ └── server.pem ├── client │ └── main.go ├── cmd │ ├── root.go │ └── server.go ├── pkg │ └── util │ │ │ ├── grpc.go │ │ │ └── tls.go ├── proto │ ├── google │ │ └── api │ │ │ ├── annotations.proto │ │ │ └── http.proto │ │ └── protobuf │ │ │ └── descriptor.proto │ ├── helloworld │ │ ├── hello.pb.go │ │ ├── hello.pb.gw.go │ │ └── hello_grpc.pb.go │ ├── hello.proto ├── server │ └── server.go ├── main.go └── Makefile
参考:https://developer.aliyun.com/article/879486 https://eddycjy.com/posts/go/grpc-gateway/2018-02-27-hello-world/