初始化项目
$ go mod init github.com/rcrick/grpc-gatewat
module github.com/rcrick/grpc-gatewat
go 1.16
require (
golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect
google.golang.org/grpc v1.40.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
)
在接下来启动server的时候,可能会有缺少go依赖的报错,直接按照提示运行go get XXXX 就好
创建相关文件夹
.
├── client
├── go.mod
├── proto
└── server
编写proto
创建proto/hello.proto文件
syntax = "proto3";
package proto;
option go_package = "github.com/rcrick/grpc-gatewat/proto";
service HelloService {
rpc SayHello(HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
$ cd proto
$ protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello.proto
会在proto目录下生成hello.pb.go和hello_grpc.pb.go
.
├── client
├── go.mod
├── proto
│ ├── hello.pb.go
│ ├── hello.proto
│ └── hello_grpc.pb.go
└── server
└── main.go
如果不加--go_opt=paths=source_relative和--go-grpc_opt=paths=source_relative
那么编译生成的文件则会放到go_package指定的路径下, 不建议这种做法
$ protoc --go_out=. --go-grpc_out=. hello.proto
结果如下
.
├── client
├── go.mod
├── proto
│ ├── github.com
│ │ └── rcrick
│ │ └── grpc-gatewat
│ │ └── proto
│ │ ├── hello.pb.go
│ │ └── hello_grpc.pb.go
编写server
package main
import (
"context"
"fmt"
"log"
"net"
"google.golang.org/grpc"
pb "github.com/rcrick/grpc-gatewat/proto"
)
type server struct {
pb.UnimplementedHelloServiceServer
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Receive msg: %s", in.GetName())
return &pb.HelloReply{Message: fmt.Sprintf("You send server: %s", in.GetName())}, nil
}
const PORT = "8000"
func main() {
s := grpc.NewServer()
pb.RegisterHelloServiceServer(s, &server{})
lis, err := net.Listen("tcp", ":"+PORT)
if err != nil {
log.Fatal(err)
}
log.Fatal(s.Serve(lis))
}
编写client
package main
import (
"context"
"fmt"
"log"
"time"
"google.golang.org/grpc"
pb "github.com/rcrick/grpc-gatewat/proto"
)
const PORT = "8000"
func main() {
conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := pb.NewHelloServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
res, err := client.SayHello(ctx, &pb.HelloRequest{Name: "I'm client"})
if err != nil {
log.Fatal(err)
}
fmt.Print(res.GetMessage())
}
验证结果
启动server
$ cd server
$ go run main.go
启动client
$ cd client
$ go run main.go
$ You send server: I'm client
支持result api
修改hello.proto文件
syntax = "proto3";
package proto;
// new add
import "google/api/annotations.proto";
option go_package = "github.com/rcrick/grpc-gatewat/proto";
service HelloService {
rpc SayHello(HelloRequest) returns (HelloReply) {
// new add
option (google.api.http) = {
get: "/v1/hello"
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
因为引用了google/api/annotations.proto[🔗,而annotations.proto又引用了google/api/http.proto🔗,
所以我直接在proto下创建文件夹google/api/,并把annotations.proto和http.proto拷贝到这里
$ cd proto
$ mkdir -p google/api/
$ cd google/api/
$ curl -O https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto
$ curl -O https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto
此时项目结构如下:
.
├── client
│ └── main.go
├── go.mod
├── go.sum
├── proto
│ ├── google
│ │ └── api
│ │ ├── annotations.proto
│ │ └── http.proto
│ └── hello.proto
└── server
└── main.go
再次编译proto文件
$ cd proto
$ protoc --go_out . --go_opt paths=source_relative \
--go-grpc_out . --go-grpc_opt paths=source_relative \
--grpc-gateway_out . --grpc-gateway_opt paths=source_relative hello.proto
如果不将依赖文件放到本地,会有如下报错:
$ google/api/annotations.proto: File not found.
$ hello.proto:6:1: Import "google/api/annotations.proto" was not found or had errors.
会生成三个文件hello.pb.gw.go,hello.pb.go,hello_grpc.pb.go
下载依赖
go get "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
.
├── client
│ └── main.go
├── go.mod
├── go.sum
├── proto
│ ├── google
│ │ └── api
│ │ ├── annotations.proto
│ │ └── http.proto
│ ├── hello.pb.go
│ ├── hello.pb.gw.go
│ ├── hello.proto
│ └── hello_grpc.pb.go
└── server
└── main.go
修改server/main.go
package main
import (
"context"
"fmt"
"log"
"net"
"net/http"
"google.golang.org/grpc"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
pb "github.com/rcrick/grpc-gatewat/proto"
)
type server struct {
pb.UnimplementedHelloServiceServer
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Receive msg: %s", in.GetName())
return &pb.HelloReply{Message: fmt.Sprintf("You send server: %s", in.GetName())}, nil
}
const PORT = "8000"
func main() {
s := grpc.NewServer()
pb.RegisterHelloServiceServer(s, &server{})
lis, err := net.Listen("tcp", ":"+PORT)
if err != nil {
log.Fatal(err)
}
go func() {
log.Fatal(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(),
":"+PORT,
grpc.WithInsecure(),
grpc.WithBlock())
if err != nil {
log.Fatal(err)
}
gwmux := runtime.NewServeMux()
err = pb.RegisterHelloServiceHandler(context.Background(), gwmux, conn)
if err != nil {
log.Fatal(err)
}
gwServer := &http.Server{
Addr: ":8001",
Handler: gwmux,
}
log.Println("Serving gRPC-Gateway on http://localhost:8001/")
log.Fatalln(gwServer.ListenAndServe())
}
验证
$ cd server
$ go run main
$ 2021/09/03 01:13:51 Receive msg:
使用请求参数进行验证
我是用的是go-proto-validators
修改hello.proto
syntax = "proto3";
package proto;
import "google/api/annotations.proto";
// new add
import "validator/validator.proto";
option go_package = "github.com/rcrick/grpc-gatewat/proto";
service HelloService {
rpc SayHello(HelloRequest) returns (HelloReply) {
// new add
option (google.api.http) = {
post: "/v1/hello"
body: "*"
};
}
}
// new add
message HelloRequest {
int32 number =1 [(validator.field) = {int_gt: 0, int_lt: 100}];
string name = 2 [(validator.field) = {length_gt: 10}];
}
message HelloReply {
string message = 1;
}
在go-proto-validators里文档里写的用法是import "github.com/mwitkow/go-proto-validators/validator.proto";
在我这里会报错,所以我自己新建一个目录,并将validator.proto放到本地,代码改成了import "validator/validator.proto";
$ cd proto
$ mkdir validator
$ cd validator
$ curl -O https://raw.githubusercontent.com/mwitkow/go-proto-validators/master/validator.proto
编译proto
$ protoc --go_out . --go_opt paths=source_relative \
--go-grpc_out . --go-grpc_opt paths=source_relative \
--grpc-gateway_out . --grpc-gateway_opt paths=source_relative \
--govalidators_out=. --govalidators_opt paths=source_relative hello.proto
多生成了一个文件hello.validator.pb.go
.
├── client
│ └── main.go
├── go.mod
├── go.sum
├── proto
│ ├── github.com
│ ├── google
│ │ └── api
│ │ ├── annotations.proto
│ │ └── http.proto
│ ├── hello.pb.go
│ ├── hello.pb.gw.go
│ ├── hello.proto
│ ├── hello.validator.pb.go
│ ├── hello_grpc.pb.go
│ └── validator
│ └── validator.proto
└── server
└── main.go
验证validators
启动server 使用curl进行验证
$ curl -X POST --form 'number="0"' --form 'name=" 34ddddddddddd2"' 'http://localhost:8001/v1/hello'
$ {"code":3, "message":"invalid character '-' in numeric literal", "details":[]
以上报错的原因是接口要求入参为json格式的,我是用的是form,所以改成json格式试一下
$ curl -X POST --data-raw '{"number": 0,"name": "34ddddddddddd2"}' 'http://localhost:8001/v1/hello'
$ {"code":2, "message":"invalid field Number: value '0' must be greater than '0'", "details":[]}
因为number要求大于0,所以再改下post的数据
$ curl -X POST --data-raw '{"number": 10,"name": "34ddddddddddd2"}' 'http://localhost:8001/v1/hello
$ {"message":"You send server: 34ddddddddddd2"}
成功.