gRPC gateway - Http Restful API & gRPC 协议转换

gRPC gateway - http restful

gRPC gateway 介绍

gRPC-Gateway 是protocalBufffer的编译插件,根据protobuf的服务定义自动生成反向代理服务器,并将Restful Http API 转化为 gRPC通信协议。反向代理服务器根据 google.api.http 注解生成。

gRPC底层是使用HTTP2 协议进行数据通信,固定为HTTP POST请求,通过gateway 方向代理可以将目前通用的http1.1转换为HTTP2协议 ,并且根据自身需要构建出符合业务场景的Http Restful Api.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9HNXxQ0g-1668943700569)(https://github.com/grpc-ecosystem/grpc-gateway/raw/master/docs/assets/images/architecture_introduction_diagram.svg)]

创建项目

初始化项目

# 1. 创建项目
mkdir grpc-gateway
# 2. 项目初始化
cd grpc-gateway
go mod init example/grpc-gateway/greetings

下载 google 依赖

# 1. 创建proto文件夹
mkdir -p proto/google/api
# 2. 下载google依赖 放置 api 文件夹
cd proto/google/api
wget https://github.com/googleapis/googleapis/tree/master/google/api/http.proto
wget https://github.com/googleapis/googleapis/tree/master/google/api/annotations.proto

创建protobuf文件

在proto文件夹下,创建example.proto

syntax = "proto3";
// go_package example/grpc-gateway/greetings 跟模块初始化名称一致方便引用
// proto 为文件目录
option go_package = "example/grpc-gateway/greetings/proto";

package proto;
import "google/api/annotations.proto";

// The greeting service definition.
service HttpGreeter {
  // Sends a greeting
  rpc SayHello (HttpHelloRequest) returns (HttpHelloReply) {
    option (google.api.http) = {
      post: "/post_hello",
      body: "*"
    };
  }
  rpc SayGetHello (HttpHelloRequest) returns (HttpHelloReply) {
    option (google.api.http) = {
      get: "/get_hello"
    };
  }
}
// The request message containing the user's name.
message HttpHelloRequest {
  string name = 1;
}
// The response message containing the greetings
message HttpHelloReply {
  string message = 1;
}

protobuf编译

  • 安装gateway依赖包

    go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2
    
  • 编译脚本

       #在项目跟目录下执行
       protoc -I=proto \
       --go_out=proto --go_opt=paths=source_relative \
       --go-grpc_out=proto --go-grpc_opt=paths=source_relative \
       --grpc-gateway_out=proto --grpc-gateway_opt=paths=source_relative \
       example.proto
    

    参数说明:

    • -I=proto - 相对目录,结合上下文,表明在proto文件夹下执行example文件夹
    • –go_out=proto - go文件的生成目录
    • –go-grpc_out=proto - grpc文件的生成目录
    • –grpc-gateway_out=proto - 反向代理服务器(grpc gateway)的生成目录

    最后使用脚本,下载相关依赖: go mod tidy

  • 完整项目结构如下

    ├── go.mod
    ├── go.sum
    ├── proto
    │   ├── example.pb.go
    │   ├── example.pb.gw.go
    │   ├── example.proto
    │   ├── example_grpc.pb.go
    │   └── google
    │       └── api
    │           ├── annotations.proto
    │           └── http.proto
    ├── proxy
    │   ├── grpc
    │   │   └── server.go
    │   └── http
    │       └── server.go
    └── server
        └── grpcServer.go
    

服务端代码

package main

import (
   "context"
   "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
   "google.golang.org/grpc"
   "google.golang.org/grpc/credentials/insecure"
   pb "google.golang.org/grpc/examples/httpget/proto"
   "log"
   "net"
   "net/http"
)

type server struct {
   pb.UnimplementedHttpGreeterServer
}

func NewServer() *server {
   return &server{}
}

func (s *server) SayHello(ctx context.Context, in *pb.HttpHelloRequest) (*pb.HttpHelloReply, error) {
   return &pb.HttpHelloReply{Message: in.Name + " world, post HttpRequest"}, nil
}

func (s *server) SayGetHello(ctx context.Context, in *pb.HttpHelloRequest) (*pb.HttpHelloReply, error) {
   return &pb.HttpHelloReply{Message: in.Name + " world, get HttpRequest"}, nil
}

func main() {
   lis, err := net.Listen("tcp", ":9999")
   if err != nil {
      log.Fatalln("Failed to listen:", err)
   }
   s := grpc.NewServer()
   pb.RegisterHttpGreeterServer(s, &server{})
   log.Println("Serving gRPC on port 9999")
   go func() {
      log.Fatalln(s.Serve(lis))
   }()

   // 构建grpc服务上下文 
   conn, err := grpc.DialContext(
      context.Background(),
      "0.0.0.0:9999",
      grpc.WithBlock(),
      grpc.WithTransportCredentials(insecure.NewCredentials()),
   )
   if err != nil {
      log.Fatalln("Failed to dial server:", err)
   }

   gwmux := runtime.NewServeMux()
   // 注册Greeter
   err = pb.RegisterHttpGreeterHandler(context.Background(), gwmux, conn)
   if err != nil {
      log.Fatalln("Failed to register gateway:", err)
   }

   gwServer := &http.Server{
      Addr:    ":8090",
      Handler: gwmux,
   }
   // 8090端口提供gRPC-Gateway服务
   log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
   log.Fatalln(gwServer.ListenAndServe())
}

出现代码报错,请执行:go mod tidy

结果测试

HTTP POST

curl -v POST -k http://localhost:8090/post_hello -d '{"name": " hello"}'

gRPC gateway - Http Restful API & gRPC 协议转换_第1张图片

HTTP GET

curl -v http://localhost:8090/get_hello/kobe

gRPC gateway - Http Restful API & gRPC 协议转换_第2张图片

服务代码分离

之前为了测试方便,服务端代码将gRPC代码、gateway代码结合在一起,这在真实的项目中是不可取的,理由是:任何一方出现系统问题都会导致整个服务不可用。分开后的代码如下:

Grpc 服务

package main

import (
   "context"
   pb "example/grpc-gateway/greetings/proto"
   "google.golang.org/grpc"
   "log"
   "net"
)

const (
   port = ":9999"
)
type server struct {
   pb.UnimplementedHttpGreeterServer
}
func NewServer() *server {
   return &server{}
}
func (s *server) SayHello(ctx context.Context, in *pb.HttpHelloRequest) (*pb.HttpHelloReply, error) {
   return &pb.HttpHelloReply{Message: in.Name + " world, post HttpRequest"}, nil
}
func (s *server) SayGetHello(ctx context.Context, in *pb.HttpHelloRequest) (*pb.HttpHelloReply, error) {
   return &pb.HttpHelloReply{Message: in.Name + " world, get HttpRequest"}, nil
}

func main() {
   lis, err := net.Listen("tcp", port)
   if err != nil {
      log.Fatalf("failed to listen: %v", err)
   }
   s := grpc.NewServer()

   pb.RegisterHttpGreeterServer(s, &server{})
   if err := s.Serve(lis); err != nil {
      log.Fatalf("failed to serve: %v", err)
   }
}

Http 服务

package main

import (
   "flag"
   "net/http"

   pb "example/grpc-gateway/greetings/proto"
   "github.com/golang/glog"
   "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
   "golang.org/x/net/context"
   "google.golang.org/grpc"
)

var (
   tasklistEndpoint = flag.String("tasklist_endpoint", "localhost:9999", "endpoint of YourService")
)

func run() error {
   ctx := context.Background()
   ctx, cancel := context.WithCancel(ctx)
   defer cancel()

   mux := runtime.NewServeMux()
   opts := []grpc.DialOption{grpc.WithInsecure()}
   //分离后的代码 使用的api不一致,仅此而已
   err := pb.RegisterHttpGreeterHandlerFromEndpoint(ctx, mux, *tasklistEndpoint, opts)
   if err != nil {
      return err
   }
   return http.ListenAndServe(":8090", mux)
}

func main() {
   flag.Parse()
   defer glog.Flush()

   if err := run(); err != nil {
      glog.Fatal(err)
   }
}

你可能感兴趣的:(Golang,Dubbo,Grpc,gateway,grpc,http,api,http协议转换)