1. gRPC提供HTTP服務
1.1 存在的意義
在某些場景下單純的RPC服務不能滿足提供的服務需求的話,還是需要提供HTTP服務作為補充,gRPC一樣可以提供HTTP
服務。
- 注意:gRPC提供的HTTP介面是基於
HTTP 2.0
的
1.2 程式碼示例
package main
import (
"fmt"
"gomicro-quickstart/grpc_server/service"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"log"
"net/http"
)
func main() {
// 1. 引用證書
tls, err := credentials.NewServerTLSFromFile("grpc_server/keys/server.crt", "grpc_server/keys/server_no_password.key")
if err != nil {
log.Fatal("服務端獲取證書失敗: ", err)
}
// 2. new一個grpc的server,並且加入證書
rpcServer := grpc.NewServer(grpc.Creds(tls))
// 3. 將剛剛我們新建的ProdService註冊進去
service.RegisterProdServiceServer(rpcServer, new(service.ProdService))
// 4. 新建一個路由,並傳入rpcServer
mux := http.NewServeMux()
mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Println(request)
rpcServer.ServeHTTP(writer, request)
})
// 5. 定義httpServer,監聽8082
httpServer := http.Server{
Addr: ":8082",
Handler: mux,
}
// 6. 以https形式監聽httpServer
httpServer.ListenAndServeTLS("grpc_server/keys/server.crt", "grpc_server/keys/server_no_password.key")
}
1.3 使用postman嘗試呼叫
執行上述的程式碼,然後postman
訪問8082
埠,提示訪問這個介面需要http/2協議
1.4 gRPC客戶端程式碼呼叫
針對上一節的客戶端呼叫的程式碼,我們不需要修改即可以直接訪問
即直接呼叫protoc產生的go檔案中的方法
我們服務端程式碼因為列印出了,http request的內容
所以我們檢視一下通過客戶端呼叫,會列印出什麼,可以看到
- 請求的路徑是
/service.ProdService/GetProductStock
,是{服務名}/{方法名}
的格式 - 協議是:http/2
2. 使用grpc-gateway同時提供HTTP和gRPC服務
2.1 前言
某些場景下需要同時要提供REST API服務
和gRPC服務
,維護兩個版本的服務顯然不太合理,所以grpc-gateway誕生了。
原理:通過protobuf的自定義option實現了一個閘道器,服務端同時開啟gRPC和HTTP 1.1服務,HTTP服務接收客戶端請求後轉換為grpc請求資料,獲取響應後轉為json資料返回給客戶端。
按照官方的結構說明如圖:
2.2 安裝
執行安裝以下三個
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go get -u github.com/golang/protobuf/protoc-gen-go
2.3 目錄結構
這裡用到了google官方Api中的兩個proto描述檔案,直接拷貝不要做修改,裡面定義了protocol buffer
擴充套件的HTTP option
,為grpc的http轉換提供支援。
|—— hello_http/
|—— client/
|—— main.go // 客戶端
|—— server/
|—— main.go // GRPC服務端
|—— server_http/
|—— main.go // HTTP服務端
|—— proto/
|—— google // googleApi http-proto定義
|—— api
|—— annotations.proto
|—— annotations.pb.go
|—— http.proto
|—— http.pb.go
|—— hello_http/
|—— hello_http.proto // proto描述檔案
|—— hello_http.pb.go // proto編譯後檔案
|—— hello_http_pb.gw.go // gateway編譯後檔案
2.4 示例程式碼
2.4.1 編寫proto描述檔案:proto/hello_http.proto
在SayHello
方法定義中增加了http option, POST
方式,路由為/example/echo
syntax = "proto3";
package hello_http;
option go_package = "hello_http";
import "google/api/annotations.proto";
// 定義Hello服務
service HelloHTTP {
// 定義SayHello方法
rpc SayHello(HelloHTTPRequest) returns (HelloHTTPResponse) {
// http option
option (google.api.http) = {
post: "/example/echo"
body: "*"
};
}
}
// HelloRequest 請求結構
message HelloHTTPRequest {
string name = 1;
}
// HelloResponse 響應結構
message HelloHTTPResponse {
string message = 1;
}
2.4.2 編譯proto
$ cd proto
# 編譯google.api
$ protoc -I . --go_out=plugins=grpc,Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor:. google/api/*.proto
# 編譯hello_http.proto
$ protoc -I . --go_out=plugins=grpc,Mgoogle/api/annotations.proto=github.com/jergoo/go-grpc-example/proto/google/api:. hello_http/*.proto
# 編譯hello_http.proto gateway
$ protoc --grpc-gateway_out=logtostderr=true:. hello_http/hello_http.proto
2.4.3 實現HTTP服務端
package main
import (
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
gw "github.com/jergoo/go-grpc-example/proto/hello_http"
)
func main() {
// 1. 定義一個context
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// grpc服務地址
endpoint := "127.0.0.1:50052"
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
// HTTP轉grpc
err := gw.RegisterHelloHTTPHandlerFromEndpoint(ctx, mux, endpoint, opts)
if err != nil {
grpclog.Fatalf("Register handler err:%v\n", err)
}
grpclog.Println("HTTP Listen on 8080")
http.ListenAndServe(":8080", mux)
}
2.4.4 實現gRPC服務端
package main
import (
"fmt"
"net"
"net/http"
pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入編譯生成的包
"golang.org/x/net/context"
"golang.org/x/net/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)
const (
// Address gRPC服務地址
Address = "127.0.0.1:50052"
)
// 定義helloService並實現約定的介面
type helloService struct{}
// HelloService Hello服務
var HelloService = helloService{}
// SayHello 實現Hello服務介面
func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
resp := new(pb.HelloResponse)
resp.Message = fmt.Sprintf("Hello %s.", in.Name)
return resp, nil
}
func main() {
listen, err := net.Listen("tcp", Address)
if err != nil {
grpclog.Fatalf("failed to listen: %v", err)
}
// 例項化grpc Server
s := grpc.NewServer()
// 註冊HelloService
pb.RegisterHelloServer(s, HelloService)
grpclog.Println("Listen on " + Address)
s.Serve(listen)
}
2.4.5 實現客戶端
package main
import (
pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)
const (
// Address gRPC服務地址
Address = "127.0.0.1:50052"
)
func main() {
// 連線
conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()
// 初始化客戶端
c := pb.NewHelloClient(conn)
// 呼叫方法
req := &pb.HelloRequest{Name: "gRPC"}
res, err := c.SayHello(context.Background(), req)
if err != nil {
grpclog.Fatalln(err)
}
grpclog.Println(res.Message)
}
2.5 執行並呼叫
依次開啟gRPC服務端和HTTP服務端
$ cd hello_http/server && go run main.go
Listen on 127.0.0.1:50052
$ cd hello_http/server_http && go run main.go
HTTP Listen on 8080
然後呼叫gRPC的客戶端
$ cd hello_http/client && go run main.go
Hello gRPC.
# HTTP 請求
$ curl -X POST -k http://localhost:8080/example/echo -d '{"name": "gRPC-HTTP is working!"}'
{"message":"Hello gRPC-HTTP is working!."}