golang和grpc的备忘录

首先

这个是为了我自己写的,与grpc的快速入门没有太大区别。
源代码:https://github.com/lightstaff/grpc_test

准备 GRPC

这里果然还是按照上述的快速入门指南来…

准备协议

在.proto文件中编写定义。本次我们定义了一个返回包含简单字符串”Hello”的结构体的服务(GetHello),并定义了一个使用流式处理机制将接收到的字符串转换为大写字母并返回的服务(UpperCharacters)。
导入的[github.com/gogo/protobuf/gogoproto/gogo.proto]文件扩展了生成器并提供了各种便利功能(以下略)。

ProtoBuf.proto – 序列化协议.proto

syntax = "proto3";

package gprc_test;

// 色々便利
import "github.com/gogo/protobuf/gogoproto/gogo.proto";

option go_package = "protobuf";
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.goproto_getters_all) = false;

// サービス定義
service GRPCTestServcie {
    // Helloと返すだけのサービス
    rpc GetHello(Empty) returns (ReplyModel) {}

    // stream経由で受けた文字列を大文字化して返すサービス
    rpc UpperCharacters(stream ReqModel) returns (stream ReplyModel) {}
}

// 空
message Empty {}

// Request
message ReqModel {
    string message = 1;
}

// Replay
message ReplyModel {
    string result = 1;
}

在上面的命令中:
通过protoc –proto_path=$GOPATH/src:$GOPATH/src/github.com/gogo/protobuf/protobuf:. –gofast_out=plugins=grpc:. ./protobuf/protobuf.proto
可以生成Go语言的协议文件(protobuf/protobuf.pb.go)

服务器端

参考protobuf/protobuf.pb.go文件,并编写服务器代码。

目前,无法使用已标准化的1.6或1.7版本的context。必须使用golang.org/x/net/context。请注意避免混淆。

服务进行中

package main

import (
    "io"
    "strings"

    pb "github.com/lightstaff/grpc_test/protobuf"

    netCtx "golang.org/x/net/context"
)

// Service model
type Service struct{}

// 単純にHelloと返す
func (s *Service) GetHello(ctx netCtx.Context, e *pb.Empty) (*pb.ReplyModel, error) {
    return &pb.ReplyModel{
        Result: "Hello",
    }, nil
}

// stream経由で受けた文字列を大文字化して返す
func (s *Service) UpperCharacters(stream pb.GRPCTestServcie_UpperCharactersServer) error {
    for {
        // streamが終了するまで受信し続ける
        req, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }

        // 受けたReqModelから大文字化してstreamにReplyModelを送信
        if err := stream.Send(&pb.ReplyModel{
            Result: strings.ToUpper(req.Message),
        }); err != nil {
            return err
        }
    }

    return nil
}

主要.go

package main

import (
    "net"
    "os"
    "os/signal"
    "syscall"

    pb "github.com/lightstaff/grpc_test/protobuf"

    "google.golang.org/grpc"
)

func main() {
    g := grpc.NewServer()
    s := &Service{}

    pb.RegisterGRPCTestServcieServer(g, s)

    errC := make(chan error)

    go func() {
        lis, err := net.Listen("tcp", ":18080")
        if err != nil {
            errC <- err
        }

        if err := g.Serve(lis); err != nil {
            errC <- err
        }
    }()

    quitC := make(chan os.Signal)
    signal.Notify(quitC, syscall.SIGINT, syscall.SIGTERM)

    select {
    case err := <-errC:
        panic(err)
    case <-quitC:
        g.Stop()
    }
}

通过这个,您可以拨号连接到localhost:18080。

客户端

客户计划使用Web服务,并使用回声功能。

控制器.go

package main

import (
    "io"
    "net/http"

    pb "github.com/lightstaff/grpc_test/protobuf"

    "github.com/labstack/echo"
    netCtx "golang.org/x/net/context"
)

// Heloと返すだけ
func GetHello(c echo.Context) error {
    sc, ok := c.(*ServiceContext)
    if !ok {
        return echo.NewHTTPError(http.StatusBadRequest, "コンテキストが取得できません")
    }

    rep, err := sc.ServiceClient.GetHello(netCtx.Background(), &pb.Empty{})
    if err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }

    return c.JSON(http.StatusOK, map[string]interface{}{
        "reply": rep.Result,
    })
}

// stream経由で受けた文字列を大文字化して返すサービスを呼び出してやりとり
func UpperCharacters(c echo.Context) error {
    sc, ok := c.(*ServiceContext)
    if !ok {
        return echo.NewHTTPError(http.StatusBadRequest, "コンテキストが取得できません")
    }

    type bodyModel struct {
        Messages []string `json:"messages"`
    }

    // JSONを変換
    var m bodyModel
    if err := c.Bind(&m); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }

    // streamを生成
    stream, err := sc.ServiceClient.UpperCharacters(netCtx.Background())
    if err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }

    // 受信はgoroutineで
    errC := make(chan error)
    resultC := make(chan *pb.ReplyModel)
    doneC := make(chan struct{})
    go func() {
        defer func() {
            close(errC)
            close(resultC)
            close(doneC)
        }()

        for {
            res, err := stream.Recv()
            if err == io.EOF {
                break
            }
            if err != nil {
                errC <- err
                return
            }

            resultC <- res
        }
    }()

    // 文字列をsteamに送る
    for _, message := range m.Messages {
        if err := stream.Send(&pb.ReqModel{
            Message: message,
        }); err != nil {
            return echo.NewHTTPError(http.StatusBadRequest, err.Error())
        }
    }

    if err := stream.CloseSend(); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }

    // この辺もうちょっとスマートに書きたい…
    results := make([]string, 0)
    for {
        select {
        case err := <-errC:
            if err != nil {
                return echo.NewHTTPError(http.StatusBadRequest, err.Error())
            }
        case result := <-resultC:
            if result != nil {
                results = append(results, result.Result)
            }
        case <-doneC:
            return c.JSON(http.StatusOK, map[string]interface{}{
                "results": results,
            })
        }
    }
}

主要的.go文件

package main

import (
    "context"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    pb "github.com/lightstaff/grpc_test/protobuf"

    "google.golang.org/grpc"
    "github.com/labstack/echo"
)

type ServiceContext struct {
    echo.Context
    ServiceClient pb.GRPCTestServcieClient
}

// このMiddlewareでGRPCにダイアル
func serviceContextMiddleware(grpcAddr string) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            cc, err := grpc.Dial(grpcAddr, grpc.WithBlock(), grpc.WithInsecure())
            if err != nil {
                return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
            }
            defer cc.Close()

            sc := &ServiceContext{
                Context:       c,
                ServiceClient: pb.NewGRPCTestServcieClient(cc),
            }

            return next(sc)
        }
    }
}

func main() {
    e := echo.New()

    e.Use(serviceContextMiddleware("localhost:18080"))

    e.GET("/hello", GetHello)
    e.POST("/upper-characters", UpperCharacters)

    errC := make(chan error)
    go func() {
        if err := e.Start(":8080"); err != nil {
            errC <- err
        }
    }()

    quitC := make(chan os.Signal)
    signal.Notify(quitC, syscall.SIGINT, syscall.SIGTERM)

    select {
    case err := <-errC:
        panic(err)
    case <-quitC:
        shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        if err := e.Shutdown(shutdownCtx); err != nil {
            errC <- err
        }
    }
}

请通过JSON将`{“messages:[“aaa”,”bbb”,”ccc”]}’`传递给`upper-characters`。需要提醒的是,在服务端返回`ReplyModel`之前,如果方法已经结束并且`ServiceClient`已被释放,服务器将失去发送目标并可能引发错误。

bannerAds