Golang gRPC實踐 連載七 HTTP協議轉換

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

gRPC HTTP協議轉換

正當有這個需求的時候,就看到了這個實現姿勢。源自coreos的一篇部落格,轉載到了grpc官方部落格gRPC with REST and Open APIs。

etcd3改用grpc後為了相容原來的api,同時要提供http/json方式的API,為了滿足這個需求,要麼開發兩套API,要麼實現一種轉換機制,他們選擇了後者,而我們選擇跟隨他們的腳步。

他們實現了一個協議轉換的網關,對應github上的項目grpc-gateway,這個網關負責接收用戶端請求,然後決定直接轉寄給grpc服務還是轉給http服務,當然,http服務也需要請求grpc服務擷取響應,然後轉為json響應給用戶端。結構

下面我們就直接實戰吧。基於hello-tls項目擴充,用戶端改動不大,服務端和proto改動較大。

安裝grpc-gateway

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway

項目結構:

$GOPATH/src/grpc-go-practice/example/|—— hello-http-2/    |—— client/        |—— main.go   // 用戶端    |—— server/        |—— main.go   // 服務端|—— keys/                 // 認證目錄    |—— server.key    |—— server.pem|—— proto/    |—— google       // googleApi http-proto定義        |—— api            |—— annotations.proto            |—— annotations.pb.go            |—— http.proto            |—— http.pb.go    |—— hello_http.proto   // proto描述檔案    |—— hello_http.pb.go   // proto編譯後檔案    |—— hello_http_pb.gw.go // gateway編譯後檔案

這裡用到了google官方Api中的兩個proto描述檔案,直接拷貝不要做修改,裡面定義了protocol buffer擴充的HTTP option,為grpc的http轉換提供支援。

範例程式碼

proto/hello_http.proto

syntax = "proto3";  // 指定proto版本package proto;     // 指定包名import "google/api/annotations.proto";// 定義Hello服務service HelloHttp {    // 定義SayHello方法    rpc SayHello(HelloHttpRequest) returns (HelloHttpReply) {        // http option        option (google.api.http) = {            post: "/example/echo"            body: "*"        };    }}// HelloRequest 請求結構message HelloHttpRequest {    string name = 1;}// HelloReply 響應結構message HelloHttpReply {    string message = 1;}

這裡在原來的SayHello方法定義中增加了http option, POST方式,路由為"/example/echo"。

編譯proto

cd $GOPATH/src/grpc-go-practice/example/hello-http-2/proto# 編譯google.apiprotoc -I . --go_out=plugins=grpc,Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor:. google/api/*.proto# 編譯hello_http.protoprotoc -I . --go_out=plugins=grpc,Mgoogle/api/annotations.proto=git.vodjk.com/go-grpc/example/proto/google/api:. ./*.proto# 編譯hello_http.proto gatewayprotoc --grpc-gateway_out=logtostderr=true:. ./hello_http.proto

注意這裡需要編譯google/api中的兩個proto檔案,同時在編譯hello_http.proto時指定引入包名,最後使用grpc-gateway編譯產生hello_http_pb.gw.go檔案,這個檔案就是用來做協議轉換的,查看檔案可以看到裡面產生的http handler,處理上面定義的路由"example/echo"接收POST參數,調用HelloHTTP服務的用戶端請求grpc服務並響應結果。

server/main.go

package mainimport (    "crypto/tls"    "fmt"    "io/ioutil"    "log"    "net"    "net/http"    "strings"    "github.com/grpc-ecosystem/grpc-gateway/runtime"    "golang.org/x/net/context"    "google.golang.org/grpc"    pb "git.vodjk.com/go-grpc/example/proto"    "google.golang.org/grpc/credentials"    "google.golang.org/grpc/grpclog")// 定義helloHttpService並實現約定的介面type helloHttpService struct{}// HelloHttpService ...var HelloHttpService = helloHttpService{}func (h helloHttpService) SayHello(ctx context.Context, in *pb.HelloHttpRequest) (*pb.HelloHttpReply, error) {    resp := new(pb.HelloHttpReply)    resp.Message = "Hello " + in.Name + "."    return resp, nil}// grpcHandlerFunc 檢查請求協議並返回http handlerfunc grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {        // TODO(tamird): point to merged gRPC code rather than a PR.        // This is a partial recreation of gRPC's internal checks https://github.com/grpc/grpc-go/pull/514/files#diff-95e9a25b738459a2d3030e1e6fa2a718R61        if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {            grpcServer.ServeHTTP(w, r)        } else {            otherHandler.ServeHTTP(w, r)        }    })}func main() {    endpoint := "127.0.0.1:50052"    // 執行個體化標準grpc server    creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key")    if err != nil {        grpclog.Fatalf("Failed to generate credentials %v", err)    }    conn, _ := net.Listen("tcp", endpoint)    grpcServer := grpc.NewServer(grpc.Creds(creds))    pb.RegisterHelloHttpServer(grpcServer, HelloHttpService)    // http-grpc gateway    ctx := context.Background()    ctx, cancel := context.WithCancel(ctx)    defer cancel()    dcreds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name")    if err != nil {        grpclog.Fatalf("Failed to create TLS credentials %v", err)    }    dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)}    gwmux := runtime.NewServeMux()    err = pb.RegisterHelloHttpHandlerFromEndpoint(ctx, gwmux, endpoint, dopts)    if err != nil {        fmt.Printf("serve: %v\n", err)        return    }    mux := http.NewServeMux()    mux.Handle("/", gwmux)    if err != nil {        panic(err)    }    // 開啟HTTP服務    cert, _ := ioutil.ReadFile("../../keys/server.pem")    key, _ := ioutil.ReadFile("../../keys/server.key")    var demoKeyPair *tls.Certificate    pair, err := tls.X509KeyPair(cert, key)    if err != nil {        panic(err)    }    demoKeyPair = &pair    srv := &http.Server{        Addr:    endpoint,        Handler: grpcHandlerFunc(grpcServer, mux),        TLSConfig: &tls.Config{            Certificates: []tls.Certificate{*demoKeyPair},        },    }    fmt.Printf("grpc and https on port: %d\n", 50052)    err = srv.Serve(tls.NewListener(conn, srv.TLSConfig))    if err != nil {        log.Fatal("ListenAndServe: ", err)    }    return}

好吧,這麼大一坨。核心就是開啟了一個http server,收到請求後檢查請求是grpc還是http,然後決定是由grpc服務直接處理還是交給gateway做轉寄處理。其中grpcHandlerFunc函數負責處理決定用哪個handler處理請求,這個方法是直接Copy過來用的,原文的注釋說他們也是從別處Copy的。感謝貢獻者。

基本流程:

  • 執行個體化標準grpc server

  • 將grpc server註冊給gateway

  • 開啟http服務,handler指定給grpcHandlerFunc方法

注意:必須開啟HTTPS

運行結果

開啟服務:

# hello-http-2/servergo run main.go> grpc and https on port: 50052    

調用grpc用戶端:

# hello-http-2/clientgo run main.go> Hello gRPC.

請求https:

curl -X POST -k https://localhost:50052/example/echo -d '{"name": "gRPC-HTTP is working!"}'> {"message":"Hello gRPC-HTTP is working!."}

為什麼是hello-http-2,因為1是個不完整的實現姿勢,可以不用https,但是需要分別開啟grpc服務和http服務,這裡不做說明了。

參考

本系列範例程式碼

  • go-grpc-example

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.