如果你仅仅是想玩玩
go-web
单机开发可以参考gin项目框架
一、微服务之间传递数据
-
1、在微服务中不仅仅是可以通过入参和返回参数来进行数据交互,另外还可以通过
metadata
的方式传递参数 -
2、定义一个简单的
proto
文件syntax = "proto3"; option go_package = ".;proto"; service HelloWorld { rpc SayHello(HelloRequest) returns(HelloResponse); } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; }
-
3、服务器端定义接口客户端传递过来的
metadata
的数据type server struct { } func (s *server) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloResponse, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { log.Fatalf("获取metadata数据失败") } if nameSlice, ok := md["name"]; ok { fmt.Println("获取到的数据", nameSlice[0]) } return &proto.HelloResponse{ Message: "hello" + in.Name, }, nil } func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("监听端口错误:" + err.Error()) } service := grpc.NewServer() proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } }
-
4、客户端定义
metadata
传递参数给服务器端func main() { conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure()) if err != nil { fmt.Println(err.Error()) return } defer conn.Close() c := proto.NewHelloWorldClient(conn) // 自定义传递的metadata参数 md := metadata.New(map[string]string{ "name": "admin", "password": "123456", }) ctx := metadata.NewOutgoingContext(context.Background(), md) response, err := c.SayHello(ctx, &proto.HelloRequest{ Name: "admin", }) if err != nil { fmt.Println(err.Error()) return } fmt.Println("服务器端返回的数据" + response.Message) }
二、拦截器的使用interceptor
-
1、所谓的拦截器是在网络请求的过程中,拦截请求和响应的,以下先配置一个简单的
protobuf
的网络请求,然后在这个基础上添加拦截器,来拦截客户端发送的数据到服务器端,也可以用来统计接口访问时长- 1、
proto
文件
syntax = "proto3"; option go_package = ".;proto"; service HelloWorld { rpc SayHello(HelloRequest) returns(HelloResponse); } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; }
- 2、使用命令生成
go
文件
protoc -I=. --go_out=plugins=grpc,paths=source_relative:. helloWorld.proto
-
3、服务器端的代码
type server struct { } func (s *server) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloResponse, error) { return &proto.HelloResponse{ Message: "hello" + in.Name, }, nil } func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("监听端口错误:" + err.Error()) } service := grpc.NewServer() proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } }
-
4、客户端
func main() { conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure()) if err != nil { fmt.Println(err.Error()) return } defer conn.Close() c := proto.NewHelloWorldClient(conn) response, err := c.SayHello(context.Background(), &proto.HelloRequest{ Name: "admin", }) if err != nil { fmt.Println(err.Error()) return } fmt.Println("服务器端返回的数据" + response.Message) }
- 1、
-
2、在服务器端定义拦截器来拦截请求,客户端的不变,启动客户端和服务器端,观察服务器端
func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("监听端口错误:" + err.Error()) } // 定义拦截器 interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { fmt.Println("接收到一个新的请求") return handler(ctx, req) } // 使用拦截器 opt := grpc.UnaryInterceptor(interceptor) service := grpc.NewServer(opt) proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } }
-
3、拦截在请求前和请求后都打印信息出来
... // 定义拦截器 interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { fmt.Println("请求前打印") res, err := handler(ctx, req) fmt.Println("请求完成打印") return res, err } // 使用拦截器 opt := grpc.UnaryInterceptor(interceptor) service := grpc.NewServer(opt) ...
-
4、客户端拦截器的使用
func main() { // 定义拦截器 interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { start := time.Now() err := invoker(ctx, method, req, reply, cc, opts...) fmt.Printf("耗时:%s\n", time.Since(start)) return err } // 使用拦截器 opt := grpc.WithUnaryInterceptor(interceptor) conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure(), opt) if err != nil { fmt.Println(err.Error()) return } defer conn.Close() c := proto.NewHelloWorldClient(conn) response, err := c.SayHello(context.Background(), &proto.HelloRequest{ Name: "admin", }) if err != nil { fmt.Println(err.Error()) return } fmt.Println("服务器端返回的数据" + response.Message) }
三、使用拦截器和metadata
来做微服务之间的授权
-
1、这里授权我们可以简单的做一个类似用户登录后才能访问的功能,把客户端想象成浏览器,把服务器端想象成之前我们熟悉的
gin-web
开发 -
2、简单的
demo
和上面的一样的,只是在基础上添加拦截器和metadata
的操作 -
3、在客户端的拦截器中传递用户名和密码到服务器端
func main() { // 定义拦截器 interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { start := time.Now() fmt.Printf("耗时:%s\n", time.Since(start)) // 使用metadata传递数据 md := metadata.New(map[string]string{ "username": "admin", "password": "123456", }) ctx = metadata.NewOutgoingContext(context.Background(), md) err := invoker(ctx, method, req, reply, cc, opts...) return err } // 使用拦截器 opt := grpc.WithUnaryInterceptor(interceptor) conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure(), opt) if err != nil { fmt.Println(err.Error()) return } defer conn.Close() c := proto.NewHelloWorldClient(conn) response, err := c.SayHello(context.Background(), &proto.HelloRequest{ Name: "admin", }) if err != nil { fmt.Println(err.Error()) return } fmt.Println("服务器端返回的数据" + response.Message) }
-
4、服务器端在拦截中获取到
metadata
中的数据并且判断用户名和密码func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("监听端口错误:" + err.Error()) } // 定义拦截器 interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { fmt.Println("请求前打印") // 解析metadata中的数据验证 md, ok := metadata.FromIncomingContext(ctx) if !ok { return resp, status.Error(codes.Unauthenticated, "无效的参数") } var ( username string password string ) if val, ok := md["username"]; ok { username = val[0] } if val, ok := md["password"]; ok { password = val[0] } fmt.Println(username, password, "接收到的参数") if username != "admin" || password != "123456" { return resp, status.Error(codes.Unauthenticated, "用户名和密码错误") } res, err := handler(ctx, req) fmt.Println("请求完成打印") return res, err } // 使用拦截器 opt := grpc.UnaryInterceptor(interceptor) service := grpc.NewServer(opt) proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } }
四、参数校验
- 1、github参考文档
- 2、这个文档还不太稳定,有需要的可以自行研究
五、返回状态吗
-
1、我们在
http
请求的时候有状态吗,自然在grpc
的时候也有状态码,来标注当前请求成功与失败 -
3、服务器端使用状态码返回给客户端端
import ( ... "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "log" "net" ) type server struct { } func (s *server) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloResponse, error) { //return &proto.HelloResponse{ // Message: "hello" + in.Name, //}, nil // 返回错误 return nil, status.Error(codes.NotFound, "记录没有找到") } func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("监听端口错误:" + err.Error()) } service := grpc.NewServer() proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } }
-
4、客户端接收错误,根据错误中的状态码提示
func main() { conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure()) if err != nil { fmt.Println(err.Error()) return } defer conn.Close() c := proto.NewHelloWorldClient(conn) response, err := c.SayHello(context.Background(), &proto.HelloRequest{ Name: "admin", }) if err != nil { str, ok := status.FromError(err) if !ok { panic("解析错误信息失败") } fmt.Println(str.Message(), "获取到的错误信息") fmt.Println(str.Code(), "获取到的错误code") fmt.Println(err.Error()) return } fmt.Println("服务器端返回的数据" + response.Message) }
六、超时处理
-
1、在客户端设置一个最大时间限制
// 设置时间 ctx, _ := context.WithTimeout(context.Background(), time.Second*3) response, err := c.SayHello(ctx, &proto.HelloRequest{ Name: "admin", })
-
2、服务器端休眠下返回数据