If you just want to play go web stand-alone development, you can refer to it gin project framework
1, Transferring data between microservices
-
1. In microservices, data can be exchanged not only through input parameters and return parameters, but also through metadata
-
2. Define a simple proto file
syntax = "proto3"; option go_package = ".;proto"; service HelloWorld { rpc SayHello(HelloRequest) returns(HelloResponse); } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; }
-
3. The server side defines the data of the metadata passed by the interface client
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("obtain metadata Data failure") } if nameSlice, ok := md["name"]; ok { fmt.Println("Acquired data", nameSlice[0]) } return &proto.HelloResponse{ Message: "hello" + in.Name, }, nil } func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("Listening port error:" + err.Error()) } service := grpc.NewServer() proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } }
-
4. The client defines metadata and passes parameters to the server
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) // Custom passed metadata parameters 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("Data returned by the server" + response.Message) }
2, Use of interceptor
-
1. The so-called interceptor intercepts requests and responses in the process of network requests. First configure a simple protobuf network request, and then add an interceptor on this basis to intercept the data sent by the client to the server. It can also be used to count the interface access time
- 1. proto file
syntax = "proto3"; option go_package = ".;proto"; service HelloWorld { rpc SayHello(HelloRequest) returns(HelloResponse); } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; }
- 2. Generate go file using command
protoc -I=. --go_out=plugins=grpc,paths=source_relative:. helloWorld.proto
-
3. Server side code
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("Listening port error:" + err.Error()) } service := grpc.NewServer() proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } }
-
4. Client
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("Data returned by the server" + response.Message) }
-
2. An interceptor is defined on the server side to intercept requests. The of the client remains unchanged. Start the client and server side and observe the server side
func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("Listening port error:" + err.Error()) } // Define interceptor interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { fmt.Println("A new request was received") return handler(ctx, req) } // Use interceptors opt := grpc.UnaryInterceptor(interceptor) service := grpc.NewServer(opt) proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } }
-
3. The interceptor prints the information before and after the request
... // Define interceptor interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { fmt.Println("Print before request") res, err := handler(ctx, req) fmt.Println("Request to finish printing") return res, err } // Use interceptors opt := grpc.UnaryInterceptor(interceptor) service := grpc.NewServer(opt) ...
-
4. Use of client interceptors
func main() { // Define interceptor 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("time consuming:%s\n", time.Since(start)) return err } // Use interceptors 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("Data returned by the server" + response.Message) }
3, Use interceptors and metadata for authorization between microservices
-
1. Here, we can simply do a function similar to the function that users can access only after logging in. Imagine the client as a browser and the server as the familiar gin web development
-
2. The simple demo is the same as the above, only adding interceptors and metadata on the basis
-
3. Pass the user name and password to the server in the interceptor on the client side
func main() { // Define interceptor 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("time consuming:%s\n", time.Since(start)) // Passing data using 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 } // Use interceptors 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("Data returned by the server" + response.Message) }
-
4. The server obtains the data in the metadata in the interception and judges the user name and password
func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("Listening port error:" + err.Error()) } // Define interceptor interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { fmt.Println("Print before request") // Parsing data validation in metadata md, ok := metadata.FromIncomingContext(ctx) if !ok { return resp, status.Error(codes.Unauthenticated, "Invalid parameter") } 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, "Received parameters") if username != "admin" || password != "123456" { return resp, status.Error(codes.Unauthenticated, "User name and password error") } res, err := handler(ctx, req) fmt.Println("Request to finish printing") return res, err } // Use interceptors opt := grpc.UnaryInterceptor(interceptor) service := grpc.NewServer(opt) proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } }
4, Parameter verification
- 1,github reference documentation
- 2. This document is not very stable. You can study it yourself if necessary
5, Return status
-
1. Do we have a status at the time of http request? Naturally, we also have a status code at the time of grpc to mark the success or failure of the current request
-
3. The server returns the status code to the client
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 error return nil, status.Error(codes.NotFound, "Record not found") } func main() { listen, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("Listening port error:" + err.Error()) } service := grpc.NewServer() proto.RegisterHelloWorldServer(service, &server{}) err = service.Serve(listen) if err != nil { fmt.Println(err.Error()) } }
-
4. The client receives an error and prompts according to the status code in the error
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("Failed to parse error message") } fmt.Println(str.Message(), "Error information obtained") fmt.Println(str.Code(), "Error getting code") fmt.Println(err.Error()) return } fmt.Println("Data returned by the server" + response.Message) }
6, Timeout processing
-
1. Set a maximum time limit on the client
// Set time ctx, _ := context.WithTimeout(context.Background(), time.Second*3) response, err := c.SayHello(ctx, &proto.HelloRequest{ Name: "admin", })
-
2. Data returned under server hibernation