Herkese merhaba!

Uzun yıllardır bol miktarda kişisel zaman ve enerji harcayarak bilgimizi hepinizle paylaşıyoruz. Ancak şu andan itibaren bu blogu çalışır durumda tutabilmek için yardımınıza ihtiyacımız var. Yapmanız gereken tek şey, sitedeki reklamlardan birine tıklamak olacaktır, aksi takdirde hosting vb. masraflar nedeniyle maalesef yayından kaldırılacaktır. Teşekkürler.

Bu örneğimizde istemci ve sunucu Golang uygulamaları için gRPC tekli engelleyici oluşturacağız. Kavram, HTTP yönlendirici ve HTTP sunucusu için ara yazılım oluşturmakla aynıdır.


Örnek basit. İstemci, yanıt almanın ne kadar sürdüğünü öğrenmek için timer engelleyici kullanacaktır. Daha sonra kimliğini ayarlamak için identity engelleyicisini kullanacaktır. Bu daha sonra sunucunun verify ismindeki engelleyicisi tarafından kontrol edilecek. Sunucuda ayrıca log adında bir engelleyici bulunur ve bu da sadece bir şeyi günlüğe kaydeder.


Yapı


├── Makefile
├── client
│   └── main.go
├── go.mod
├── hello.pb.go
├── hello.proto
├── interceptor
│   ├── Identity.go
│   ├── log.go
│   ├── timer.go
│   └── verify.go
├── message
│   ├── client.go
│   └── server.go
└── server
└── main.go

Dosyalar


Makefile


hello.pb.go dosyasını yaratmak için make compile komutunu çalıştırın.


.PHONY: compile
compile:
protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative hello.proto

.PHONY: client
client:
go run --race client/main.go

.PHONY: server
server:
go run --race server/main.go

hello.proto


syntax = "proto3";

package message;

option go_package = "generated;generated";

message MessageRequest {
string text = 1;
}

message MessageResponse {
string text = 1;
}

service MessageService {
rpc SendMessage (MessageRequest) returns (MessageResponse) {}
}

client/main.go


package main

import (
"context"
"log"
"time"

"google.golang.org/grpc"
"sport/interceptor"
"sport/message"
)

func main() {
log.Println("client")

opts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithChainUnaryInterceptor(
interceptor.TimerUnaryClient,
interceptor.Identity{ID: "client-1"}.UnaryClient,
),
}

conn, err := grpc.Dial(":50051", opts...)
if err != nil {
log.Fatalln(err)
}
defer conn.Close()

messageClient := message.NewClient(conn, time.Second*10)
res, err := messageClient.SendMessage(context.Background(), "Hello")

log.Println("ERRR:", err)
log.Println("RESP:", res)
}

interceptor/timer.go


package interceptor

import (
"context"
"log"
"time"

"google.golang.org/grpc"
)

func TimerUnaryClient(
ctx context.Context,
method string,
req interface{},
reply interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
start := time.Now()

log.Println("Timer: 1")
time.Sleep(time.Second)

err := invoker(ctx, method, req, reply, cc, opts...)

log.Println("Timer: 2")
time.Sleep(time.Second)

end := time.Since(start)

log.Printf("%s method call took %s\n", method, end)

return err
}

interceptor/Identity.go


package interceptor

import (
"context"
"log"
"time"

"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)

type Identity struct {
ID string
}

func (i Identity) UnaryClient(
ctx context.Context,
method string,
req interface{},
reply interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
md := metadata.Pairs()
md.Set("client-id", i.ID)

ctx = metadata.NewOutgoingContext(ctx, md)

log.Println("Identity: 1")
time.Sleep(time.Second)

err := invoker(ctx, method, req, reply, cc, opts...)

log.Println("Identity: 2")
time.Sleep(time.Second)

return err
}

message/client.go


package message

import (
"context"
"time"

"google.golang.org/grpc"

generated "sport"
)

type Client struct {
messageClient generated.MessageServiceClient
timeout time.Duration
}

func NewClient(conn grpc.ClientConnInterface, timeout time.Duration) Client {
return Client{
messageClient: generated.NewMessageServiceClient(conn),
timeout: timeout,
}
}

func (c Client) SendMessage(ctx context.Context, message string) (*generated.MessageResponse, error) {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(c.timeout))
defer cancel()

return c.messageClient.SendMessage(ctx, &generated.MessageRequest{Text: message})
}

server/main.go


package main

import (
"log"
"net"

"google.golang.org/grpc"
"sport/interceptor"
"sport/message"

generated "sport"
)

func main() {
log.Println("server")

listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln(err)
}

opts := []grpc.ServerOption{
grpc.ChainUnaryInterceptor(
interceptor.VerifyUnaryServer,
interceptor.LogUnaryServer,
),
}

grpcServer := grpc.NewServer(opts...)

messageServer := message.NewServer()

generated.RegisterMessageServiceServer(grpcServer, messageServer)

log.Fatalln(grpcServer.Serve(listener))
}

interceptor/verify.go


package interceptor

import (
"context"
"log"
"time"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)

func VerifyUnaryServer(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (resp interface{}, err error) {
log.Println("Verify: 1")
time.Sleep(time.Second)

md, ok := metadata.FromIncomingContext(ctx)
if !ok || len(md["client-id"]) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "missing metadata")
}
if md["client-id"][0] != "client-1" {
return nil, status.Error(codes.PermissionDenied, "unexpected client")
}

res, err := handler(ctx, req)

log.Println("Verify: 2")
time.Sleep(time.Second)

return res, err
}

interceptor/log.go


package interceptor

import (
"context"
"log"
"time"

"google.golang.org/grpc"
)

func LogUnaryServer(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (resp interface{}, err error) {
log.Println("Log: 1")
time.Sleep(time.Second)

log.Println("Logging")

res, err := handler(ctx, req)

log.Println("Log: 2")
time.Sleep(time.Second)

return res, err
}

message/server.go


package message

import (
"context"

generated "sport"
)

type Server struct {
generated.UnimplementedMessageServiceServer
}

func NewServer() Server {
return Server{}
}

func (s Server) SendMessage(ctx context.Context, req *generated.MessageRequest) (*generated.MessageResponse, error) {
return &generated.MessageResponse{Text: req.GetText()}, nil
}

Test


Önce make server daha sonra make client komutunu çalıştırın. Her iki komut çıktısının kombinasyonu aşağıdaki gibi görünecektir. Bu, önleyicilerin hangi sırayla nasıl çalıştıklarını gösterir.


                    CLIENT       SERVER
2020/09/14 19:11:46 Timer: 1
2020/09/14 19:11:47 Identity: 1
2020/09/14 19:11:48 Verify: 1
2020/09/14 19:11:49 Log: 1
2020/09/14 19:11:50 Log: 2
2020/09/14 19:11:51 Verify: 2
2020/09/14 19:11:52 Identity: 2
2020/09/14 19:11:53 Timer: 2