1. 安装插件
protoc/usr/local/include/google/protobufprotoc-gen-go
protoc-gen-grpc-gatewayprotoc-gen-swagger
[root@CentOS ~]# go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
2. 服务定义文件中添加 REST 注释
grpc-go-tutorial/restful-api/userpb/service.proto
syntax = "proto3";

option go_package="userpb";

package user;

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

// User define a user
message User {
    string username = 1;
    string password = 2;
}

// CreateRequest is the request for creating a user.
message CreateRequest {
    User user = 1;
}

// GetRequest is the request for getting a user.
message GetRequest {
    string username = 1;
}

// GetRequest is the response for getting a user.
message GetResponse {
    User user = 1;
}

// UserService is the user service.
service UserService {
    // Create a new user
    rpc Create(CreateRequest) returns (google.protobuf.Empty) {
        option (google.api.http) = {
            post: "/api/v1/users"
            body: "*"
        };
    }
    // Get a specified user
    rpc Get(GetRequest) returns (GetResponse) {
        option (google.api.http) = {
            get: "/api/v1/users/{username}"
        };
    }
}

我们在 UserService 服务的 Create 方法下,添加了 REST 注释,标注它支持 HTTP POST 操作,同样的标注 Get 方法支持 HTTP GET 操作

service.pb.go
[root@CentOS userpb]# pwd
/root/grpc-go-tutorial/restful-api/userpb
[root@CentOS userpb]# protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --go_out=plugins=grpc:. \
  service.proto
service.pb.gw.go
[root@CentOS userpb]# protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --grpc-gateway_out=logtostderr=true:. \
  service.proto
3. gRPC 与 REST 监听不同的端口

gRPC 与 REST 监听不同的端口.jpg

3.1 实现 gRPC 服务端

grpc-go-tutorial/restful-api/server/main.go
// Package main implements a server for User service.
package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "net"

    "google.golang.org/grpc/credentials"

    "github.com/golang/protobuf/ptypes/empty"
    "google.golang.org/grpc/codes"

    pb "github.com/wangy8961/grpc-go-tutorial/restful-api/userpb"
    "google.golang.org/grpc"
)

// server is used to implement pb.UserServiceServer.
type server struct {
    users map[string]pb.User
}

// NewServer creates User service
func NewServer() pb.UserServiceServer {
    return &server{
        users: make(map[string]pb.User),
    }
}

// Create a new user
func (s *server) Create(ctx context.Context, req *pb.CreateRequest) (*empty.Empty, error) {
    log.Println("--- Creating new user... ---")
    log.Printf("request received: %v\n", req)

    user := req.GetUser()
    if user.Username == "" {
        return nil, grpc.Errorf(codes.InvalidArgument, "username cannot be empty")
    }
    if user.Password == "" {
        return nil, grpc.Errorf(codes.InvalidArgument, "password cannot be empty")
    }

    s.users[user.Username] = *user

    log.Println("--- User created! ---")
    return &empty.Empty{}, nil
}

// Get a specified user
func (s *server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
    log.Println("--- Getting user... ---")

    if req.Username == "" {
        return nil, grpc.Errorf(codes.InvalidArgument, "username cannot be empty")
    }

    u, exists := s.users[req.Username]
    if !exists {
        return nil, grpc.Errorf(codes.NotFound, "user not found")
    }

    log.Println("--- User found! ---")
    return &pb.GetResponse{User: &u}, nil
}

func main() {
    port := flag.Int("port", 50051, "the port to serve on")
    certFile := flag.String("certfile", "server.crt", "Server certificate")
    keyFile := flag.String("keyfile", "server.key", "Server private key")
    flag.Parse()

    lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) // Specify the port we want to use to listen for client requests
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    fmt.Printf("server listening at %v\n", lis.Addr())

    creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
    if err != nil {
        log.Fatalf("failed to load certificates: %v", err)
    }

    s := grpc.NewServer(grpc.Creds(creds)) // Create an instance of the gRPC server

    pb.RegisterUserServiceServer(s, NewServer()) // Register our service implementation with the gRPC server
    if err := s.Serve(lis); err != nil {         // Call Serve() on the server with our port details to do a blocking wait until the process is killed or Stop() is called.
        log.Fatalf("failed to serve: %v", err)
    }
}

3.2 (可选) 实现客户端

grpc-go-tutorial/restful-api/client/main.go
// Package main implements a client for User service.
package main

import (
    "time"
    "context"
    "flag"
    "log"

    "google.golang.org/grpc/credentials"

    pb "github.com/wangy8961/grpc-go-tutorial/restful-api/userpb"
    "google.golang.org/grpc"
)

func createUserCall(client pb.UserServiceClient, username, password string) {
    log.Println("--- gRPC Create RPC Call ---")

    // 设置 10 秒超时时长,可参考 https://madmalls.com/blog/post/grpc-deadline/#21-contextwithtimeout
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    // 调用 Create RPC
    req := &pb.CreateRequest{
        User: &pb.User{
            Username: username,
            Password: password,
        },
    }
    resp, err := client.Create(ctx, req)
    if err != nil {
        log.Fatalf("failed to call Create RPC: %v", err)
    }

    log.Println("response:")
    log.Printf(" - %q\n", resp)
}

func getUserCall(client pb.UserServiceClient, username string) {
    log.Println("--- gRPC Get RPC Call ---")

    ctx, cancel := context.WithTimeout(context.Background(), 10