go实现rpc和http服务
01 准备工作
下面两个工具,用来从pb文件生成数据结构和grpc服务代码
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
02 新建项目
新建一个项目,在项目目录下执行go mod init命令,完成go module初始化。
03 使用protobuf定义gRPC服务
syntax = "proto3";
package user;
//新版本需要的配置
option go_package = "./user";
import "google/api/annotations.proto";
message UserRequest {
int32 id = 1;
}
message UserResponse {
int32 id = 1;
string name = 2;
}
service UserService {
rpc GetUser(UserRequest) returns (UserResponse) {};
}
04 生成代码
protoc ./proto/user.proto \
--go_out=./proto \
--go-grpc_out=./proto --go-grpc_opt=require_unimplemented_servers=false
05 写server端和client端代码
server端
-
声明一个结构体,结构体里引用生成代码中的UnimplementedXXX接口
-
实现一个创建结构体示例的方法NewXXX
-
给这个结构体实现方法
-
写一个main方法,grpc服务启动和注册服务
// UserServer 定义接口结 构体,直接引用生成的桩代码里的接口定义
type UserServer struct {
user.UnimplementedUserServiceServer
}
// NewUserServer 创建一个接口实例
func NewUserServer() *UserServer {
return &UserServer{}
}
// GetUser 实现接口
func (userService *UserServer) GetUser(ctx context.Context, req *user.UserRequest) (*user.UserResponse, error) {
response := &user.UserResponse{
Id: req.Id,
Name: "hello world",
}
return response, nil
}
// startGrpc 启动grpc服务
func startGrpc(ctx context.Context) {
// Create a listener on TCP port
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *grpcPort))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 创建一个gRPC server对象
s := grpc.NewServer()
// 注册user server到server
user.RegisterUserServiceServer(s, internal.NewUserServer())
log.Println(fmt.Sprintf("server listening at %v", lis.Addr()))
// 启动gRPC Server
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
client端
-
创建链接,创建client
-
调用接口
func main() {
flag.Parse()
// set up a connection to server.
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := user.NewUserServiceClient(conn)
res, _ := client.GetUser(context.Background(), &user.UserRequest{Id: 1})
resp, err := json.Marshal(res)
fmt.Printf("%s", resp)
}
至此,一个grpc服务的服务端,客户端,都已经完成,并且能运行了。
06 使用gRPC-gateway为gRPC暴露http接口
gRPC-gateway是protobuf编译器protoc的插件,它先读取pb文件中的service定义的内容,并生成反向代理服务器(代码),该服务可以将rest api转换为gRPC。
目前有三种方式可以生成反向代理服务器(代码)
-
不做任何修改直接生成,protoc-gen-grpc-gateway会按照默认规则映射Method和参数等HTTP配置
-
给protobuf添加annotations,可以自定义Method和Path等HTTP配置
-
使用外部配置,比较适用于不能修改源protobuf的情况下
我们使用第二种方式。
安装gRPC-gateway
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
代码生成
因为使用的是非内置的pb定义google/api/annotations.proto,需要在生成前先添加pb依赖,从官方仓库复制对应的pb文件到项目里。
https://github.com/googleapis/googleapis/blob/master/google/api/annotations.proto
https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
然后在proto文件中引入import "google/api/annotations.proto";,并且在proto的service的方法中增加option块。
rpc GetUser(UserRequest) returns (UserResponse) {
//在这里添加google.api.http注释
option (google.api.http) = {
get: "/api/getuser"
};
};
运行命令
protoc ./proto/proto/user.proto \
--go_out=./proto \
--go-grpc_out=./proto --go-grpc_opt=require_unimplemented_servers=false \
--grpc-gateway_out=./proto
会在proto目录下额外生成一个user.pb.gw.go文件。 新生成的文件中,有注册相关访问,调用即可实现http接口的转发。
server端代码
下面两种方式都可以
// startGateWay 第一种方式,先启动gRPC,在以gRPC服务为基础创建gRPC-gateway服务
func startGateWay(ctx context.Context) {
//创建一个连接到我们刚刚启动的 gRPC 服务器的客户端连接
conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%v", *grpcPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
// 创建一个mux并注册
mux := runtime.NewServeMux()
if err := user.RegisterUserServiceHandler(ctx, mux, conn); err != nil {
log.Fatalln("Failed to register gateway:", err)
}
//创建并启动http server
gwServer := &http.Server{
Addr: fmt.Sprintf(":%d", *httpPort),
Handler: mux,
}
log.Println(fmt.Sprintf("gateway listening at %v", gwServer.Addr))
if err := gwServer.ListenAndServe(); err != nil {
log.Fatalln("Failed to start gateway:", err)
}
}
// startGateWay2 第2种方式,直接使用本地函数调用方法访问gRPC
func startGateWay2(ctx context.Context) {
// 创建一个mux并注册,使用本地函数调用方式
mux := runtime.NewServeMux()
if err := user.RegisterUserServiceHandlerServer(ctx, mux, internal.NewUserServer()); err != nil {
log.Fatalln("Failed to register gateway2:", err)
}
//创建并启动http server
gwServer := &http.Server{
Addr: fmt.Sprintf(":%d", *httpPort),
Handler: mux,
}
log.Println(fmt.Sprintf("gateway2 listening at %v", gwServer.Addr))
if err := gwServer.ListenAndServe(); err != nil {
log.Fatalln("Failed to start gateway2:", err)
}
}
附录:buf使用
buf是方便我们管理proto文件依赖,生成代码的工具。buf命令,要在proto目录下使用 安装buf
通过
brew install buf安装buf工具
初始化buf
在proto目录下,使用
buf mod init创建一个buf.yaml文件
添加依赖
在此文件中添加依赖
buf.build/googleapis/googleapisdeps:
- buf.build/googleapis/googleapis
更新依赖
buf mod updatebuf从Buf Schema Registry(BSR)中获取依赖,把所有的deps更新到最新版,并且会生成buf.lock来固定版本
代码生成
创建一个
buf.gen.yamlversion: v1
plugins:
- plugin: go
out: ./
- plugin: go-grpc
out: ./
- name: grpc-gateway
out: ./然后执行
buf generate ././可以省略,会在当前目录下生成需要的go文件。
参考文章:文章中描述了对请求的精细化定制、生成swagger文件、同一个端口同时实现gRPC和http服务等内容,有需要再继续学习。 https://juejin.cn/post/7291245343269339151 https://www.liwenzhou.com/posts/Go/grpc-gateway/