一、gateway
閘道器(Gateway)又稱網間聯結器、協議轉換)器。閘道器在網路層以上實現網路互連,是複雜的網路互連裝置,僅用於兩個高層協議不同的網路互連。閘道器既可以用於廣域網互連,也可以用於區域網互連。 閘道器是一種充當轉換重任的計算機系統或裝置。使用在不同的通訊協議、資料格式或語言,甚至體系結構完全不同的兩種系統之間,閘道器是一個翻譯器。與網橋只是簡單地傳達資訊不同,閘道器對收到的資訊要重新打包,以適應目的系統的需求。同層–應用層。
簡單的說就是:從一個房間走到另一個房間,必然要經過一扇門。同樣,從一個網路向另一個網路傳送資訊,也必須經過一道“關口”,這道關口就是閘道器
grpc-gateway 能夠將我們寫好的grpc服務轉換為外部可以訪問的http服務,這樣一來我們就對外暴露grpc服務的介面,當然grpc內部依然需要使用grpc通訊。
二、gateway的實現
我們來模擬行程獲取的過程,內部使用grpc將服務寫好,使用grpc向外暴露http介面
首先我們需要知道:
- 起點
- 終點
- 距離
- 費用
- 起點終點座標
- 路徑座標
下面我們來編寫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()方法不會退出
}
編寫客戶端:
- 這裡也可以透過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)
}
- 透過瀏覽器:
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 協議》,轉載必須註明作者和本文連結