[golang]grpc-gateway使用入门

gRPC-Gateway介绍

gRPC简单实现

  1. 定义proto文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    syntax = "proto3";

    package auth.v1;

    option go_package = "coolcar/auth/api/gen/v1;authpb";

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

    service AuthService {
    // 注解方式
    // rpc Login (LoginRequest) returns (LoginResponse) {
    // option(google.api.http) = {
    // post: "/v1/auth/login",
    // body: "*"
    // };
    // };

    // 也可以通过auth.yaml配置文件形式
    rpc Login (LoginRequest) returns (LoginResponse);
    }


    message LoginRequest {
    string code = 1;
    }

    message LoginResponse {
    string access_token = 1;
    int32 expires_in = 2;
    }
  2. 根据文件生成相应代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # go程序代码
    protoc -I . --go_out gen/v1 --go_opt paths=source_relative auth.proto
    # grpc服务代码
    protoc -I . --go-grpc_out gen/v1 --go-grpc_opt paths=source_relative auth.proto
    # grpc-gateway代码
    protoc -I . --grpc-gateway_out gen/v1 --grpc-gateway_opt paths=source_relative,grpc_api_configuration=auth.yaml auth.proto

    # 三个命令可合为一个
    protoc -I . \
    --go_out gen/v1 \
    --go_opt paths=source_relative \
    --go-grpc_out gen/v1 \
    --go-grpc_opt paths=source_relative \
    --grpc-gateway_out gen/v1 \
    --grpc-gateway_opt paths=source_relative,grpc_api_configuration=auth.yaml \
    auth.proto

    生成代码文件

    1
    2
    3
    4
    5
    6
    7
    ├── auth.proto  # proto
    ├── auth.yaml # gateway 配置文件
    └── gen
    └── v1
    ├── auth_grpc.pb.go # grpc服务方法
    ├── auth.pb.go # 消息体文件
    └── auth.pb.gw.go # grpc-gateway代理文件
  3. 实现grpc服务接口方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package auth

    import (
    "context"
    authpb "coolcar/auth/api/gen/v1"
    )

    type Service struct {
    // 不要问,继承这个空服务就行了。看稳定是为了兼容后续
    *authpb.UnimplementedAuthServiceServer
    }

    func (s *Service) Login(ctx context.Context, req *authpb.LoginRequest) (*authpb.LoginResponse, error) {
    return &authpb.LoginResponse{
    AccessToken: "access_token for " + req.Code,
    ExpiresIn: 7200,
    }, nil
    }

  4. 拉起grpc服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package main

    import (
    authpb "coolcar/auth/api/gen/v1"
    "coolcar/auth/auth"
    "log"
    "net"

    "google.golang.org/grpc"
    )

    func main() {
    // 监听TCP端口号
    lis, err := net.Listen("tcp", ":8081")
    if err != nil {
    log.Fatal(err)
    }
    // 创建一个grpc服务
    s := grpc.NewServer()
    // 注册grpc服务
    authpb.RegisterAuthServiceServer(s, &auth.Service{})
    // 拉起服务
    log.Fatal(s.Serve(lis))
    }

  5. 调用grpc服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    func startClient() {
    conn, err := grpc.Dial("localhost:8081", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
    log.Fatalln("连接服务失败:", err)
    }
    defer conn.Close()

    client := authpb.NewAuthServiceClient(conn)

    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    rsp, err := client.Login(ctx, &authpb.LoginRequest{Code: "123456"})
    if err != nil {
    log.Fatalln("调用方法失败:", err)
    }
    fmt.Printf("调用结果为: access_token = %v, expire_in = %v", rsp.AccessToken, rsp.ExpiresIn)
    }

为gRPC服务添加Gateway

前端程序不能直接调用grpc服务,所以使用grpc-gateway网关反向代理

  1. 方式一
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    func registerHandler() {
    // 创建一个客户端连接到grpc服务器,然后用grpc-gateway代理请求反向代理到这个客户端
    conn, err := grpc.DialContext(
    context.Background(),
    "localhost:8081",
    grpc.WithBlock(),
    grpc.WithTransportCredentials(insecure.NewCredentials()),
    )
    if err != nil {
    log.Fatalln("连接grpc服务器失败", err)
    }

    // 创建路由处理器
    gwmux := runtime.NewServeMux(runtime.WithMarshalerOption(
    runtime.MIMEWildcard,
    &runtime.JSONPb{
    MarshalOptions: protojson.MarshalOptions{
    Multiline: false, // 多行显示,Indent
    Indent: "", // 缩进
    AllowPartial: false,
    UseProtoNames: false, // 使用原型proto里定义的字段名
    UseEnumNumbers: false, // 使用枚举enum对应的数字
    EmitUnpopulated: false, // 是否返回空(未填充)的字段
    Resolver: nil,
    },
    },
    ))

    err = authpb.RegisterAuthServiceHandler(context.Background(), gwmux, conn)
    if err != nil {
    log.Fatal("注册grpc-gateway代理失败:", err)
    }

    // 监听http服务
    log.Fatal("启动http服务失败:", http.ListenAndServe(":8080", gwmux))
    }
  2. 方式二
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    func registerHandlerFromEndPoint() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // 创建路由处理器
    gwmux := runtime.NewServeMux(runtime.WithMarshalerOption(
    runtime.MIMEWildcard,
    // JSONPb对JSON进行编组/解组。与JSONBuiltin不同,它支持protobuf的全部功能。
    &runtime.JSONPb{
    MarshalOptions: protojson.MarshalOptions{
    // Multiline: false, // 多行显示,Indent
    // Indent: "", // 缩进
    // AllowPartial: false,
    UseProtoNames: true, // 使用原型proto里定义的字段名
    UseEnumNumbers: true, // 使用枚举enum对应的数字
    EmitUnpopulated: true, // 是否返回空(未填充)的字段
    // Resolver: nil,
    },
    },
    ))

    // 注册服务
    err := authpb.RegisterAuthServiceHandlerFromEndpoint(ctx, gwmux, "localhost:8081", []grpc.DialOption{
    grpc.WithBlock(),
    grpc.WithTransportCredentials(insecure.NewCredentials()),
    })

    if err != nil {
    log.Fatalln("注册服务失败", err)
    }

    // 监听http服务
    err = http.ListenAndServe(":8080", gwmux)
    if err != nil {
    log.Fatal("启动http服务失败:", err)
    }
    }
  3. 发起http请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    curl -X POST \
    'http://127.0.0.1:8080/v1/auth/login' \
    --header 'Accept: */*' \
    --header 'User-Agent: Thunder Client (https://www.thunderclient.com)' \
    --header 'Content-Type: application/json' \
    --data-raw '{
    "code": "123"
    }'

    #返回
    {
    "accessToken": "access_token for 123",
    "expiresIn": 7200
    }

gRPC-Gateway代理配置

  1. 注解方式(google.api.http)
    需要加入依赖文件googleapis
    1
    2
    3
    4
    5
    6
    7
    proto
    ├── google
    │ └── api
    │ ├── annotations.proto
    │ └── http.proto
    └── helloworld
    └── hello_world.proto
    protobuf 文件修改加入注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    syntax = "proto3";

    package helloworld;

    import "google/api/annotations.proto";

    // Here is the overall greeting service definition where we define all our endpoints
    service Greeter {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
    post: "/v1/example/echo"
    body: "*"
    };
    }
    }

    // The request message containing the user's name
    message HelloRequest {
    string name = 1;
    }

    // The response message containing the greetings
    message HelloReply {
    string message = 1;
    }
  2. yaml配置文件
    文档地址:https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/grpc_api_configuration/
    1
    2
    3
    4
    5
    6
    7
    8
    type: google.api.Service
    config_version: 3

    http:
    rules:
    - selector: your.service.v1.YourService.Echo
    post: /v1/example/echo
    body: "*"