快速构建浏览器兼容的 gRPC 服务
前言⌗
虽然 gRPC 已经比较成熟了,但是在前端领域的兼容性问题依然存在,目前的解决方案就两种,一种是在后端使用 RESTful API 服务对 gRPC 服务再做一层封装,另一种方法是直接在前端使用 gRPC-Web 之类的项目,实现前端直接调用 gRPC!
为了让项目既能够支持 RESTful API 又能够支持 gRPC,人们创造了 gRPC-Gateway 这个项目,主要就是为了解决 RESTful 反向代理 gRPC 服务路由的问题。
无论哪种方式,对于刚接触的开发者来说,都有一定的心智负担。后来在一个开源项目中我发现他们使用的是有 Buf 开源的 ConnectRPC 项目来实现这一层的兼容。
ConnectRPC 的 slogan 是:
Connect is a family of libraries for building browser and gRPC-compatible APIs. If you’re tired of hand-written boilerplate and turned off by massive frameworks, Connect is for you.
安装所需工具⌗
mkdir example
cd example
go mod init example
go install github.com/bufbuild/buf/cmd/buf@latest
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
定义服务⌗
创建一个 greet/v1/greet.proto
文件,内容如下:
syntax = "proto3";
package greet.v1;
option go_package = "example/gen/greet/v1;greetv1";
message GreetRequest {
string name = 1;
}
message GreetResponse {
string greeting = 1;
}
service GreetService {
rpc Greet(GreetRequest) returns (GreetResponse) {}
}
生成代码⌗
首先为项目生成 buf.yaml
:
version: v2
plugins:
- local: protoc-gen-go
out: gen
opt: paths=source_relative
- local: protoc-gen-connect-go
out: gen
opt: paths=source_relative
然后运行 buf generate
命令生成代码:
gen
└── greet
└── v1
├── greet.pb.go
└── greetv1connect
└── greet.connect.go
实现业务逻辑⌗
package main
import (
"context"
"fmt"
"log"
"net/http"
"connectrpc.com/connect"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
greetv1 "example/gen/greet/v1" // generated by protoc-gen-go
"example/gen/greet/v1/greetv1connect" // generated by protoc-gen-connect-go
)
type GreetServer struct{}
func (s *GreetServer) Greet(ctx context.Context, req *connect.Request[greetv1.GreetRequest]) (*connect.Response[greetv1.GreetResponse], error) {
log.Println("Request headers: ", req.Header())
res := connect.NewResponse(&greetv1.GreetResponse{
Greeting: fmt.Sprintf("Hello, %s!", req.Msg.Name),
})
res.Header().Set("Greet-Version", "v1")
return res, nil
}
func main() {
greeter := &GreetServer{}
mux := http.NewServeMux()
path, handler := greetv1connect.NewGreetServiceHandler(greeter)
mux.Handle(path, handler)
// Use h2c so we can serve HTTP/2 without TLS.
http.ListenAndServe("localhost:8080", h2c.NewHandler(mux, &http2.Server{}))
}
安装依赖并运行⌗
go get golang.org/x/net/http2
go get connectrpc.com/connect
go run ./cmd/server/main.go
发送请求⌗
下面是基于 RESTFul 的 JSON 请求:
curl \
--header "Content-Type: application/json" \
--data '{"name": "Jane"}' \
http://localhost:8080/greet.v1.GreetService/Greet
下面是基于 gRPC 的 Protobuf 请求:
grpcurl \
-protoset <(buf build -o -) -plaintext \
-d '{"name": "Jane"}' \
localhost:8080 greet.v1.GreetService/Greet
总结⌗
到这里,基本上就已经完成了对 RESTful API 和 gRPC 的兼容,但是默认的路由与 RESTful API 规范是背道而驰的,要想做到符合 RESTful API 的规范,还得使用 connectrpc/vanguard-go 这个库。
Vanguard 是一个功能强大的 Go net/http 服务器库,可实现 REST 和 RPC 协议之间的无缝转码。无论您需要弥合 gRPC、gRPC-Web、Connect 还是 REST 之间的差距,Vanguard 都能满足您的需求。借助对 Google 的 HTTP 转码选项的支持,它可以使用强类型 Protobuf 定义轻松转换协议。
与 gRPC-Gateway 等传统方法不同,Vanguard 可在 Go 服务器中高效运行,并与 Connect 和 gRPC 等各种服务器兼容。它不依赖于广泛的代码生成,因此无需额外的代码生成步骤。这种灵活性可确保您的代码能够动态适应,从配置、架构注册表或通过 gRPC 服务器反射加载服务定义,使其非常适合代理,而无需在每次 RPC 服务架构更改时重新编译和重新部署。
I hope this is helpful, Happy hacking…