gRPC HTTP protocol conversion
Just as there is the demand to see the realization of this posture. Coreos from a blog, reproduced to grpc official blog GRPC with REST APIs and Open .
After etcd3 use grpc for compatibility with the original api, while providing API http / json way, in order to meet this demand, either to develop two sets of API, or to implement a conversion mechanism, they chose the latter, but we choose to follow them footsteps.
They achieved a gateway protocol conversion, the corresponding item on GitHub grpc-Gateway , the gateway is responsible for receiving client requests, and then decided to forward directly to the grpc service or transferred to http service, of course, also need to request the service http grpc service acquisition response and then converted to json response to the client. FIG Structure:
Here we directly combat it. Based hello-tls extension project, the client modifications are minor, the server and the larger proto changes.
Installation grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
Project structure:
$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编译后文件
This uses two proto official google profile of Api, do not do a direct copy of the modification, which defines the protocol buffer extension of HTTP option, to provide support for http conversion of grpc.
Sample Code
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; }
Here in the original SayHello
adds http option defined method, POST way, the route is "/ example / echo".
Compile proto
cd $GOPATH/src/grpc-go-practice/example/hello-http-2/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=git.vodjk.com/go-grpc/example/proto/google/api:. ./*.proto # 编译hello_http.proto gateway protoc --grpc-gateway_out=logtostderr=true:. ./hello_http.proto
Note that you need to compile two proto file google / api in, specifying the package name is introduced when compiling hello_http.proto, grpc-gateway using the last compiled hello_http_pb.gw.go
file that is used for protocol conversion, you can see the view file generated inside http handler, routing process defined above "example / echo" POST parameters received calls and services HelloHTTP grpc service client requests and respond to the results.
server/main.go
package main
import (
"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 handler func 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 }
Well, so much cook. The core is to open an http server, after receiving a request to check request is grpc or http, then the decision is directly handled by the service or to the gateway do grpc forwarding process. Which grpcHandlerFunc
function is responsible for the decision which handler to process the request with this approach is straightforward Copy over with, the original notes say they are elsewhere Copy of. Thank contributors.
The basic process:
-
Examples of standards grpc server
-
The grpc server registered to the gateway
-
Open http service, handler assigned to grpcHandlerFunc method
Note: You must turn on HTTPS
operation result
Start the service:
# hello-http-2/server
go run main.go
> grpc and https on port: 50052
Call grpc client:
# hello-http-2/client
go run main.go
> Hello gRPC.
Request https:
curl -X POST -k https://localhost:50052/example/echo -d '{"name": "gRPC-HTTP is working!"}' > {"message":"Hello gRPC-HTTP is working!."}
Why hello-http-2, because 1 is not a complete implementation posture, you can not https, but need to turn grpc services and are http services, not explained here.