Grpc Gateway

grpc-gateway-demo

Protocol Buffer 官方文档

官方文档

语法指南

protoc下载地址

REST API

谷歌文档

grpc-gateway 官方文档

官方文档

github readme

Buf 文档

官方文档

swagger-ui

安装文档

protoc-gen-validate

github

Protocol Buffers的基本使用

什么是Protocol buffers

Protocol Buffer是一个由 Google 开发的协议,允许结构化数据的序列化和反序列化协议缓冲。谷歌开发它的目的是提供一种比 XML 更好的方式来使系统进行通信。因此,他们致力于使其比 XML 更简单、更小、更快、更易于维护。这个协议甚至超过了 JSON,具有更好的性能、更好的可维护性和更小的体积。

使用Protocol Buffers的好处

Protocol buffer 可以支持多语言,以及跨平台

  • 解析快
  • 传输速度快,体积小
  • 支持多语言

支持的语言包括

下载安装Protocol Buffers 编译器

通过github release页面,下载protoc

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MBzweb76-1688630613695)(https://img.zhouwanderder.xyz/uPic/image-20230115110519332.png)]

打开release页面,我们可以直接找到protoc的安装包,将其下载并且配置即可

安装 Go protocol buffers 生成插件

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

编译产生的插件protoc-gen-go会安装在$GOBIN下,默认就是$GOPATH/bin路径下

Protocol Buffer基本语法

首先我们需要创建一个proto文件,在proto文件中,我们需要通过syntax关键字来指定我们需要用到的protocol buffers的版本。除此之外,我们还需要添加option go_package来指定生成的包地址,package字段使用于proto之间的依赖指定的路径,同时proto文件中的每一行都需要使用;结尾

定义消息类型

我们这里定义一个Person类型的消息

// 指定proto语言版本
syntax = "proto3";

// 生成go文件的包路径
option go_package = "/pb";

// proto文件包的路径
package grpc.gateway.demo.proto.examplepb;


message Person {
    int32 id = 1;
    string name = 2;
    string email = 3;
    float salary = 4;
    bool sex = 5;
}

通过protoc生成go文件

protoc -I=. --go_out=. ./proto/examplepb/person.proto

通过上面编写的简单proto文件,我们可以发现,proto文件中定义message与我们创建一个go中的结构体类似,包括内部对元素的类型定义,我们也是非常熟悉的。但是我们也还有一些出去基本类型之外的复杂类型,例如:枚举、map、数组等,我们需要如何去定义

repeated

repeated关键字的作用是用来定义数组,使用方式是repeated 数组类型 属性名称 = 字段编号;

message Person {
	repeated string name = 1;
}
map

map类型的定义方式是map <键类型,值类型> 属性名称 = 字段编号; ,这里需要注意对于map的键类型,只能定义为基本数据类型,但是值的类型可以是任何支持的类型

message Person {
	map <string, Pet> pets =1;
}

message Pet {
	string name = 1;
}
enum

对于枚举的定义,我们需要用到enum关键字。

enum PhoneType {
    PHONE_TYPE_UNSPECIFIED = 0;
    PHONE_TYPE_WORK = 1;
    PHONE_TYPE_HOME = 2;
}

在枚举的定义中,我们需要指定一个零值 PHONE_TYPE_UNSPECIFIED = 0;

序列化与反序列化

package proto

import (
   "fmt"
   "log"
   "os"
   "testing"

   "google.golang.org/protobuf/proto"

   "grpc-gateway-demo/pb"
)

var fName = "person.txt"

func TestPerson(t *testing.T) {
   person := &pb.Person{}
   phone := &pb.Phone{}
   pet := &pb.Pet{
      Name: "Leo",
      Age:  1,
   }

   phone.PhoneNumber = "1234567789"
   phone.PhoneType = pb.PhoneType_PHONE_TYPE_WORK
   // phone.PhoneType = 1

   person.Name = "Ethan"
   person.Email = "xxxx@yeah.net"
   person.Phone = phone
   person.Pets = map[string]*pb.Pet{}
   person.Pets[pet.Name] = pet

   b, err := proto.Marshal(person)
   if err != nil {
      log.Fatalf("proto marshal failed: %v", err)
      return
   }

   f, err := os.Create(fName)
   defer f.Close()
   if err != nil {
      log.Fatalf("os create failed: %v", err)
      return
   }

   _, err = f.Write(b)
   if err != nil {
      log.Fatalf("write file failed: %v", err)
      return
   }

}

func TestReadPerson(t *testing.T) {
   b, err := os.ReadFile(fName)
   if err != nil {
      log.Fatalf("read file failed: %v", err)
      return
   }
   p := &pb.Person{}
   err = proto.Unmarshal(b, p)
   if err != nil {
      log.Fatalf("proto unmarshal failed: %v", err)
      return
   }

   fmt.Println(p)
}

grpc-gateway的基本使用

简介

grpc-gateway是protoc的以一个插件,它会读取proto文件中的grpc服务的定义并将其生成RestFul JSON API,并将其反向代理到我们的grpc服务中

grpc-gatway是在grpc上做的一个拓展。但是grpc并不能很好的支持客户端,以及传统的RESTful API。因此grpc-gateway诞生了,该项目可以为我们的grpc服务提供HTTP+JSON接口。

插件安装

首先,我们在项目中去创建一个tools的文件,然后运行go mod tidy

package tools

import (
    _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
    _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
    _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
    _ "google.golang.org/protobuf/cmd/protoc-gen-go"
)

最后我们运行go install将其安装在我们的$GOBIN目录下

go install \
    github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
    github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
    google.golang.org/protobuf/cmd/protoc-gen-go \
    google.golang.org/grpc/cmd/protoc-gen-go-grpc

grpc 插件安装

grpc文档

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

使用

一、定义proto文件

syntax = "proto3";

option go_package = "/pb;";

package grpc.gateway.demo.proto.examplepb;

import "google/api/annotations.proto";

// 导入api注释依赖

// 定义服务
service BookService {
    // 创建书籍
    rpc CreateBook (CreateBookRequest) returns (CreateBookResponse) {
        option (google.api.http) = {
            post: "/v1/books"
            body: "*"
        };
    };

    // 获取书籍
    rpc GetBook (GetBookRequest) returns (GetBookResponse) {
        option (google.api.http) = {
            get: "/v1/books/{id}"
        };
    }


//    rpc UpdateBook (UpdateBookRequest) returns (UpdateBookResponse) {
//
//    };
//
//
//    rpc DeleteBook (DeleteBookRequest) returns (DeleteBookResponse) {
//
//    };
//
//    rpc ListBook (ListBookRequest) returns (ListBookResponse) {};


}

message Book {
    // int64 会自动转换为string 进行返回
    int32 id = 1;
    string name = 2;

}

// 定义接收参数
message CreateBookRequest {
    string name = 1;

}


message CreateBookResponse {
    string code = 1;
    string message = 2;
    Book data = 3;
}


message GetBookRequest {
    int32 id = 1;
}


message GetBookResponse {
    string code = 1;
    string message = 2;
    Book data = 3;
}

我们在使用grpc-gateway这个框架的时候,需要使用proto文件,将我们的grpc服务进行http+json的一个暴露,以此来对外达到一个提供http+json服务的接口

二、生成go文件

在编写好proto文件之后,我们需要使用插件将proto文件生成对应的go文件。

由于我们依赖了google的proto文件,所以我们在使用protoc生成go文件的时候,需要将依赖的proto文件复制到我们的项目中,依赖的proto文件仓库google/api

google/api/annotations.proto
google/api/field_behavior.proto
google/api/http.proto
google/api/httpbody.proto

我们还需要依赖的google/protobuf

google/protobuf/descriptor.proto
protoc -I . --grpc-gateway_out=../gen \
    --grpc-gateway_opt logtostderr=true \
    --grpc-gateway_opt paths=source_relative \
    --go_out=../gen \
    --go_opt paths=source_relative \
    --go-grpc_out=../gen \
    --go-grpc_opt paths=source_relative \
    ./examplepb/book.proto

将需要依赖的文件放入项目中,我们就可以使用protoc生成go文件,这里需要用到三个插件

  • go
  • grpc
  • grpc-gateway

三、启动服务

实现BookService
package service

import (
	"context"
	"errors"
	"sync"

	pb "grpc-gateway-demo/gen/examplepb"
)

type BookService struct {
	pb.UnimplementedBookServiceServer
}

func NewBookService() *BookService {
	l := localStorage{
		Count: 0,
		DB:    make(map[int32]*pb.Book),
	}
	db = &l
	return &BookService{}
}

var db *localStorage

type localStorage struct {
	Count int32
	DB    map[int32]*pb.Book
	mux   sync.Mutex
}

func (l *localStorage) getId() int32 {
	l.Count = l.Count + 1
	return l.Count
}

func (l *localStorage) Store(d *pb.Book) error {
	if d == nil {
		return errors.New("data is nil")
	}

	if d.Id <= 0 {
		return errors.New("illegal id")
	}
	l.DB[d.Id] = d
	return nil
}

func (l *localStorage) Load(id int32) (*pb.Book, error) {
	if id <= 0 {
		return nil, errors.New("illegal id")
	}
	book := l.DB[id]
	return book, nil
}

func (b *BookService) CreateBook(ctx context.Context, req *pb.CreateBookRequest) (*pb.CreateBookResponse, error) {
	resp := &pb.CreateBookResponse{}
	db.mux.Lock()
	defer db.mux.Unlock()
	id := db.getId()
	book := pb.Book{
		Name: req.GetName(),
		Id:   id,
	}

	err := db.Store(&book)
	if err != nil {
		return resp, err
	}
	resp.Data = &book
	return resp, nil
}

func (b *BookService) GetBook(ctx context.Context, req *pb.GetBookRequest) (*pb.GetBookResponse, error) {
	resp := &pb.GetBookResponse{}
	db.mux.Lock()
	defer db.mux.Unlock()

	book, err := db.Load(req.GetId())
	if err != nil {
		return resp, err
	}
	resp.Data = book
	return resp, nil
}

启动gRPC
package server

import (
   "fmt"
   "io"
   "net"
   "os"

   "google.golang.org/grpc"
   "google.golang.org/grpc/grpclog"

   "grpc-gateway-demo/example/book/config"
   "grpc-gateway-demo/example/book/service"
   pb "grpc-gateway-demo/gen/examplepb"
)

// Run 启动rpc服务
func Run() error {
   log := grpclog.NewLoggerV2(os.Stdout, io.Discard, io.Discard)
   grpclog.SetLoggerV2(log)
   grpcAddr := config.GetRpcAddr()
   l, err := net.Listen("tcp", grpcAddr)
   if err != nil {
      grpclog.Errorf("tcp listen failed: %v", err)
      return err
   }
   defer func() {
      if err = l.Close(); err != nil {

         fmt.Fprintln(os.Stderr, err)
      }
   }()

   s := grpc.NewServer()

   //     注册服务
   registerServer(s)
   log.Infof("Serving gRPC on %s", l.Addr())
   return s.Serve(l)
}

func registerServer(s *grpc.Server) {
   pb.RegisterBookServiceServer(s, service.NewBookService())
}
启动gateway
package gateway

import (
   "context"
   "io"
   "net/http"
   "os"

   "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
   "google.golang.org/grpc"
   "google.golang.org/grpc/credentials/insecure"
   "google.golang.org/grpc/grpclog"

   "grpc-gateway-demo/example/book/config"
   pb "grpc-gateway-demo/gen/examplepb"
)

func Run() error {
   log := grpclog.NewLoggerV2(os.Stdout, io.Discard, io.Discard)
   grpclog.SetLoggerV2(log)
   ctx := context.Background()
   // 创建grpc连接
   conn, err := grpc.DialContext(ctx, config.GetRpcAddr(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   if err != nil {
      log.Error("dial failed: %v", err)
      return err
   }
   mux := runtime.NewServeMux()
   err = newGateway(ctx, conn, mux)
   if err != nil {
      log.Error("register handler failed: %v", err)
      return err
   }
   server := http.Server{
      Addr:    config.GetHttpAddr(),
      Handler: mux,
   }
   log.Infof("Serving Http on %s", server.Addr)
   err = server.ListenAndServe()
   if err != nil {
      return err
   }
   return nil
}

func newGateway(ctx context.Context, conn *grpc.ClientConn, mux *runtime.ServeMux) error {

   err := pb.RegisterBookServiceHandler(ctx, mux, conn)
   if err != nil {
      return err
   }
   return nil
}
启动main
package main

import (
   "fmt"
   "os"

   "grpc-gateway-demo/example/book/gateway"
   "grpc-gateway-demo/example/book/server"
)

func main() {
   go func() {
      err := server.Run()
      if err != nil {
         fmt.Fprintln(os.Stderr, err)
         os.Exit(1)
      }
   }()

   err := gateway.Run()
   fmt.Fprintln(os.Stderr, err)
   os.Exit(1)

}

自定义路由

有些时候,rpc的服务不能满足我们的需求,比如文件上传下载,使用proto文件定义api以及实现是无法实现的,这个时候需要我们额外的添加上自定义路由来完成相关操作。

使用中间件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃胡萝卜的代码兔

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值