「gRPC」 Gateway的實現

ice_moss發表於2022-03-13
一、gateway

閘道器(Gateway)又稱網間聯結器、協議轉換)器。閘道器在網路層以上實現網路互連,是複雜的網路互連裝置,僅用於兩個高層協議不同的網路互連。閘道器既可以用於廣域網互連,也可以用於區域網互連。 閘道器是一種充當轉換重任的計算機系統或裝置。使用在不同的通訊協議、資料格式或語言,甚至體系結構完全不同的兩種系統之間,閘道器是一個翻譯器。與網橋只是簡單地傳達資訊不同,閘道器對收到的資訊要重新打包,以適應目的系統的需求。同層–應用層。

簡單的說就是:從一個房間走到另一個房間,必然要經過一扇門。同樣,從一個網路向另一個網路傳送資訊,也必須經過一道“關口”,這道關口就是閘道器

grpc-gateway 能夠將我們寫好的grpc服務轉換為外部可以訪問的http服務,這樣一來我們就對外暴露grpc服務的介面,當然grpc內部依然需要使用grpc通訊。

二、gateway的實現

我們來模擬行程獲取的過程,內部使用grpc將服務寫好,使用grpc向外暴露http介面

首先我們需要知道:

  1. 起點
  2. 終點
  3. 距離
  4. 費用
  5. 起點終點座標
  6. 路徑座標

下面我們來編寫trip.proto

syntax = "proto3";  //語法使用proto3
package coolcar;
option go_package = "coolcar/proto/gen/go/;trippb";

message Location{
    double latitude = 1;
    double longitude = 2;
}

enum TripStatus{
    TS_NOT_SPECIFID = 0;
    NOT_STARTED = 1;
    IN_PROGRESS = 2;
    FINISHED = 3;
    PAID = 4;
}

message Trip{
    string statar = 1;  //數字表示標記碼,不是賦值
    Location statar_pos = 5;
    repeated Location path_locations = 7;
    string end = 2;
    Location end_pos = 6;
    int32 duration_sec = 3;
    int32 fee_cent = 4;
    TripStatus status = 8;

}

message GetTripRequest{
    string id = 1;
}

message GetTripResponse{
    string id = 1;
    Trip trip = 2;
}

service TripService{
    rpc GetTrip (GetTripRequest) returns (GetTripResponse);
}

在編譯前我們需要寫一個trip.yaml檔案用來配置對外http服務向外暴露埠

type: google.api.Service
config_version: 3

http:
  rules:
  - selector: coolcar.TripService.GetTrip
    get: /trip/{id}

然後我們編寫一個trip.sh檔案用來執行我們的編譯命令吧

protoc -I=. --go_out=plugins=grpc,paths=source_relative:gen/go trip.proto
protoc -I=. --grpc-gateway_out=paths=source_relative,grpc_api_configuration=trip.yaml:gen/go trip.proto

編譯過後在gen/go下會生成trip.pb.go檔案和trip.pb.gw.go檔案

具體程式碼在:gRPC Gateway的實現

相應的客戶端和服務端程式碼就已經為我們生成了

我們只需要實現服務端的介面:

// TripServiceServer is the server API for TripService service.
type TripServiceServer interface {
    GetTrip(context.Context, *GetTripRequest) (*GetTripResponse, error)
}

介面的實現:

import (
    "context"
    trippb "coolcar/proto/gen/go"
)

//type TripServiceServer interface {
//    GetTrip(context.Context, *GetTripRequest) (*GetTripResponse, error)
// }

type Service struct{}

func (*Service) GetTrip(con context.Context, req *trippb.GetTripRequest) (*trippb.GetTripResponse, error) {
    return &trippb.GetTripResponse{
    //客戶請求什麼Id,服務端返回什麼Id
        Id: req.Id,
        Trip: &trippb.Trip{
            Statar:      "北京"
            End:         "上海",
            DurationSec: 3600,
            FeeCent:     1000,
            StatarPos: &trippb.Location{
                Latitude:  30,
                Longitude: 120,
            },
            EndPos: &trippb.Location{
                Latitude:  40,
                Longitude: 125,
            },
            PathLocations: []*trippb.Location{
                {
                    Latitude:  34,
                    Longitude: 123,
                },
                {
                    Latitude:  38,
                    Longitude: 124,
                },
            },
            Status: trippb.TripStatus_FINISHED,
        },
    }, nil
}

現在我們來實現獲取行程的整個過程:

編寫service端和gateway

package main

import (
    "context"
    trippb "coolcar/proto/gen/go"
    trip "coolcar/tripservice"
    "fmt"
    "log"
    "net"
    "net/http"

    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "google.golang.org/grpc"
)

//gateway的實現
func startGRPCGatway() {
    c := context.Background()          //生成沒具體內容的上下文
    c, cancel := context.WithCancel(c) //該方法將具有cancel的能力
    defer cancel()

    //runtime.WithMarshalerOption()將Status: trippb.TripStatus_FINISHED,改為status:3
    mux := runtime.NewServeMux(runtime.WithMarshalerOption(
        runtime.MIMEWildcard, &runtime.JSONPb{
            EnumsAsInts: true, //status
            OrigName:    true, //命名
        },
    ))

    err := trippb.RegisterTripServiceHandlerFromEndpoint(
        c, //透過context去連線, 註冊在runtime.NewServeMux()上面
        mux,
        ":8081",  //連線內部grpc服務埠
        []grpc.DialOption{grpc.WithInsecure()}, //grpc.WithInsecure()連線方式tcp明文,即不做安全處理
    )
    if err != nil {
        log.Fatalf("斷開連線: %v", err)
    }

    //對外暴露http埠
    err = http.ListenAndServe(":8080", mux)
    if err != nil {
        log.Fatalf("連線失敗: %v", err)
    }
}


//service 端
func main() {
    fmt.Println("監聽開始")
    go startGRPCGatway()
    list, err := net.Listen("tcp", ":8081")
    if err != nil {
        log.Fatalf("監聽失敗: %v", err)
    }
    s := grpc.NewServer() //NewServer 建立一個未註冊服務且尚未開始接受請求的 gRPC 伺服器。
    trippb.RegisterTripServiceServer(s, &trip.Service{})
    fmt.Println("監聽結束")
    fmt.Println(list)
    log.Fatal(s.Serve(list)) //s.Serve()方法不會退出

}

編寫客戶端:

  1. 這裡也可以透過grpc進行撥號:
package main

import (
    "context"
    trippb "coolcar/proto/gen/go"
    "fmt"
    "log"

    "google.golang.org/grpc"
)

func main() {
  //連線gateway
    con, err := grpc.Dial("localhost:8080")
    if err != nil {
        log.Fatalf("連線失敗: %v", err)
    }

    tsClient := trippb.NewTripServiceClient(con)
    res, err := tsClient.GetTrip(context.Background(), &trippb.GetTripRequest{
        Id: "trips01",
    })
    if err != nil {
        log.Fatalf("未獲取到trips: %v", err)
    }
    fmt.Println(res)
}
  1. 透過瀏覽器:http://localhost:8080/trip?Id=trips01

這樣整個流程就完了

返回結果:

id:"trips01" trip:{statar:"北京" statar_pos:{latitude:30 longitude:120} path_locations:{latitude:34 longitude:123} path_locations:{latitude:38 longitude:124} end:"上海" end_pos:{latitude:40 longitude:125} duration_sec:3600 fee_cent:1000 status:FINISHED}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章