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 örnekte bir gRPC istemci ve sunucu uygulaması oluşturacağız. İstemci, karmaşık çok boyutlu bir JSON istek mesajı gönderecek ve sunucudan basit bir yanıt mesajı alacak. Bazı alanların kendi proto mesajları olacak ve bazılarının tamamen bilinmeyen/rastgele alanlar olacaktır.


Burada kullandığımız iki önemli paket var. Karmaşık alanları işlemek için protobuf paketi. JSON marshal ve unmarshal işlemleri için jsonpb paketi. Bağlantılar için aşağı doğru kaydırın.


İstek


Bu tam olarak göndereceğimiz şey. Sadece blogu kısa tutmak için bir dosyadan okunacaktır. Uygulama içinde client/request.json olarak saklanır.


{
"name": "The Premier League",
"founded_at": "1992-02-200T22:40:36Z",
"is_active": true,
"budget": 1234567890.99,
"mascot": "Lion",
"stadiums": {
"City Ground": "Nottingham Forest",
"Manchester United": "Old Trafford",
"National": "Wembley Stadium"
},
"sponsors": [
"EA Sports",
"Coca Cola",
"Nike"
],
"awards": [
{
"type": "Trophy",
"name": "The champion"
},
{
"type": "Medal",
"name": "The runner up"
}
],
"address": {
"line1": "Brunel Building",
"line2": "57 North Wharf Road",
"line3": "",
"postcode": "W2 1HQ",
"county": "London"
},
"other": {
"concacaf": false,
"confederation": "UEFA",
"founder": null,
"random_array": [
"one",
2,
true,
false,
null
],
"random_json": {
"key_1": "value",
"key_2": 2,
"key_3": true,
"key_4": false,
"key_5": null
},
"uefa": true,
"world_ranking": 4
},
"plain": "{\"key_1\":\"value\",\"key_2\":2,\"key_3\":true,\"key_4\":false,\"key_5\":null}"
}

Yapı


├── Makefile
├── client
│   ├── main.go
│   └── request.json
├── football
│   ├── client.go
│   └── server.go
├── go.mod
├── pkg
│   └── protobuf
│   └── football
│   ├── league
│   │   ├── address.pb.go
│   │   ├── address.proto
│   │   ├── award.pb.go
│   │   ├── award.proto
│   │   ├── league.pb.go
│   │   └── league.proto
│   ├── response.pb.go
│   ├── response.proto
│   ├── service.pb.go
│   └── service.proto
└── server
└── main.go

Dosyalar


Makefile


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


.PHONY: compile
compile:
protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative pkg/protobuf/football/league/*.proto
protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative pkg/protobuf/football/*.proto

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

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

go.mod


module github.com/inanzzz/sport

go 1.15

require (
github.com/golang/protobuf v1.4.2
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect
golang.org/x/text v0.3.3 // indirect
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
google.golang.org/grpc v1.31.1
google.golang.org/protobuf v1.25.0
)

pkg/protobuf/football/league/address.proto


syntax = "proto3";

package football;

option go_package = "github.com/inanzzz/sport/pkg/protobuf/football/league";

message Address {
string line1 = 1;
string line2 = 2;
string line3 = 3;
string postcode = 4;
string county = 5;
}

pkg/protobuf/football/league/award.proto


syntax = "proto3";

package football;

option go_package = "github.com/inanzzz/sport/pkg/protobuf/football/league";

message Award {
enum Type {
None = 0;
Trophy = 1;
Medal = 2;
}

Type type = 1;
string name = 2;
}

pkg/protobuf/football/league/league.proto


syntax = "proto3";

package football;

option go_package = "github.com/inanzzz/sport/pkg/protobuf/football/league";

import "pkg/protobuf/football/league/address.proto";
import "pkg/protobuf/football/league/award.proto";

import "google/protobuf/struct.proto";

// You could import "well-known" google/protobuf/timestamp.proto package and
// replace string with google.protobuf.Timestamp type below for founded_at.

message CreateLeagueRequest {
string name = 1;
string founded_at = 2;
bool is_active = 3;
double budget = 4;
string mascot = 5;
map stadiums = 6;
repeated string sponsors = 7;
repeated Award awards = 8;
Address address = 9;
google.protobuf.Struct other = 10; // an unknown set of json key/value pairs
google.protobuf.Value plain = 11; // an unknown json.RawMessage string
}

//message UpdateLeagueRequest {}
//message FindLeagueRequest {}
//message DeleteLeagueRequest {}

pkg/protobuf/football/response.proto


syntax = "proto3";

package football;

option go_package = "github.com/inanzzz/sport/pkg/protobuf/football";

// You could import "well-known" google/protobuf/any.proto package and
// replace bytes with google.protobuf.Any type below.

message Response {
enum Result {
SUCCESS = 0;
ERROR = 1;
}

message Success {
bytes data = 1;
}

message Error {
string message = 1;
bytes errors = 2;
}

Result result = 1;
Success success = 2;
Error error = 3;
}

pkg/protobuf/football/service.proto


syntax = "proto3";

package football;

option go_package = "github.com/inanzzz/sport/pkg/protobuf/football";

import "pkg/protobuf/football/response.proto";
import "pkg/protobuf/football/league/league.proto";

service FootballService {
rpc CreateLeague(CreateLeagueRequest) returns (Response) {}

// rpc UpdateLeague(UpdateLeagueRequest) returns (Response) {}
// rpc FindLeague(FindLeagueRequest) returns (Response) {}
// rpc DeleteLeague(DeleteLeagueRequest) returns (Response) {}
}

client/main.go


package main

import (
"bytes"
"context"
"io/ioutil"
"log"
"time"

"github.com/inanzzz/sport/football"
"google.golang.org/grpc"
)

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

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

json, err := ioutil.ReadFile("client/request.json")
if err != nil {
log.Fatalln(err)
}

footballClient := football.NewClient(conn, time.Second)
err = footballClient.CreateLeague(context.Background(), bytes.NewBuffer(json))

log.Println("ERR:", err)
}

football/client.go


package football

import (
"context"
"fmt"
"io"
"log"
"time"

"github.com/inanzzz/sport/pkg/protobuf/football"
"github.com/inanzzz/sport/pkg/protobuf/football/league"

"github.com/golang/protobuf/jsonpb"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)

type Client struct {
footballClient football.FootballServiceClient
timeout time.Duration
}

func NewClient(conn grpc.ClientConnInterface, timeout time.Duration) Client {
return Client{
footballClient: football.NewFootballServiceClient(conn),
timeout: timeout,
}
}

func (c Client) CreateLeague(ctx context.Context, json io.Reader) error {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(c.timeout))
defer cancel()

req := league.CreateLeagueRequest{}
if err := jsonpb.Unmarshal(json, &req); err != nil {
return fmt.Errorf("client create league: unmarshal: %w", err)
}

res, err := c.footballClient.CreateLeague(ctx, &req)
if err != nil {
if er, ok := status.FromError(err); ok {
return fmt.Errorf("client create league: code: %s - msg: %s", er.Code(), er.Message())
}
return fmt.Errorf("client create league: %w", err)
}

log.Println("RESULT:", res.Result)
log.Println("RESPONSE:", res)

return nil
}

server/main.go


package main

import (
"log"
"net"

"github.com/inanzzz/sport/football"
"google.golang.org/grpc"

protofootball "github.com/inanzzz/sport/pkg/protobuf/football"
)

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

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

grpcServer := grpc.NewServer()
footballServer := football.NewServer()

protofootball.RegisterFootballServiceServer(grpcServer, footballServer)

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

football/server.go


package football

import (
"bytes"
"context"
"log"

"github.com/inanzzz/sport/pkg/protobuf/football"
"github.com/inanzzz/sport/pkg/protobuf/football/league"

"github.com/golang/protobuf/jsonpb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

type Server struct {
football.UnimplementedFootballServiceServer
}

func NewServer() Server {
return Server{}
}

func (s Server) CreateLeague(ctx context.Context, req *league.CreateLeagueRequest) (*football.Response, error) {
json := bytes.Buffer{}
// OrigName uses the actual field names from the proto files rather than casting them to camelCase.
// EmitDefaults prevents discarding empty/nullable fields and keeps zero values.
mars := jsonpb.Marshaler{OrigName: true, EmitDefaults: true}
if err := mars.Marshal(&json, req); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "server create league: marshal: %v", err)
}

log.Println("REQUEST:", json.String())

return &football.Response{
Result: football.Response_SUCCESS,
Success: &football.Response_Success{
Data: []byte("good job"),
},
}, nil
}

Test


Server


make server

Client


make client

Sonuç


Başlangıçta gösterilen JSON dosyasını gönderdiğinizde, sunucu tam olarak aynı JSON içeriğini çıkaracaktır. Eğer {} veya tüm alanları null değerlerle dolu bir JSON gönderirseniz, sonuç aşağıdaki gibi olacak.


# Request 1
{}

# Request 2
{
"name": null,
"founded_at": null,
"is_active": null,
"budget": null,
"mascot": null,
"stadiums": null,
"sponsors": null,
"awards": null,
"address": null,
"other": null,
"plain": null
}

Gördüğünüz gibi, tüm alanlar sıfır değerlerine ayarlanmıştır.


# Result

{
"name": "",
"founded_at": "",
"is_active": false,
"budget": 0,
"mascot": "",
"stadiums": {

},
"sponsors": [

],
"awards": [

],
"address": null,
"other": null,
"plain": null
}

Referanslar