
如果我们要起一个同时支持grpc和http协议的服务,以前的做法是使用grpc-gateway,这种做法除了引入额外组件更麻烦外,还多了一层rpc调用,带来了性能上的损耗。其实grpc是基于http协议传输的,因此我们没有必要在传输层分流,在应用层分流的话,就可以做到同一个服务里既支持grpc协议,又支持http协议。connectrpc就是一个不错的选择,其仓库位于:http://github.com/connectrpc/connect-go
下面我们分析下代码,看看如何使用,首先需要初始化项目
mkdir connect-go-example
cd connect-go-example
go mod init example安装依赖的工具,这里主要是引入了protoc-gen-connect-go 的protoc插件,可以辅助生成connectrpc代码
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然后定义我们的测试proto
mkdir -p greet/v1
touch greet/v1/greet.protosyntax = "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项目
% buf config init配置buf生成工具,需要本地gen-go插件和gen-connect-go插件
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 lint
buf generate生成后源码结构如下
|____greet
| |____v1
| | |____greet.proto
|____go.mod
|____buf.yaml
|____go.sum
|____gen
| |____greet
| | |____v1
| | | |____greetv1connect
| | | | |____greet.connect.go
| | | |____greet.pb.go
|____buf.gen.yaml对应的.connect.go源码如下,可以看到,它的源码很简单,就是包装了下client和server的源码接口。
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
//
// Source: greet/v1/greet.proto
package greetv1connect
import (
context "context"
errors "errors"
v1 "example/gen/greet/v1"
"fmt"
http "net/http"
strings "strings"
connect "connectrpc.com/connect"
)
// This is a compile-time assertion to ensure that this generated file and the connect package are
// compatible. If you get a compiler error that this constant is not defined, this code was
// generated with a version of connect newer than the one compiled into your binary. You can fix the
// problem by either regenerating this code with an older version of connect or updating the connect
// version compiled into your binary.
const _ = connect.IsAtLeastVersion1_13_0
const (
// GreetServiceName is the fully-qualified name of the GreetService service.
GreetServiceName = "greet.v1.GreetService"
)
// These constants are the fully-qualified names of the RPCs defined in this package. They're
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
//
// Note that these are different from the fully-qualified method names used by
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
// period.
const (
// GreetServiceGreetProcedure is the fully-qualified name of the GreetService's Greet RPC.
GreetServiceGreetProcedure = "/greet.v1.GreetService/Greet"
)
// GreetServiceClient is a client for the greet.v1.GreetService service.
type GreetServiceClient interface {
Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error)
}
// NewGreetServiceClient constructs a client for the greet.v1.GreetService service. By default, it
// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
// connect.WithGRPCWeb() options.
//
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
// http://api.acme.com or https://acme.com/grpc).
func NewGreetServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) GreetServiceClient {
baseURL = strings.TrimRight(baseURL, "/")
greetServiceMethods := v1.File_greet_v1_greet_proto.Services().ByName("GreetService").Methods()
return &greetServiceClient{
greet: connect.NewClient[v1.GreetRequest, v1.GreetResponse](
httpClient,
baseURL+GreetServiceGreetProcedure,
connect.WithSchema(greetServiceMethods.ByName("Greet")),
connect.WithClientOptions(opts...),
),
}
}
// greetServiceClient implements GreetServiceClient.
type greetServiceClient struct {
greet *connect.Client[v1.GreetRequest, v1.GreetResponse]
}
// Greet calls greet.v1.GreetService.Greet.
func (c *greetServiceClient) Greet(ctx context.Context, req *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error) {
return c.greet.CallUnary(ctx, req)
}
// GreetServiceHandler is an implementation of the greet.v1.GreetService service.
type GreetServiceHandler interface {
Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error)
}
// NewGreetServiceHandler builds an HTTP handler from the service implementation. It returns the
// path on which to mount the handler and the handler itself.
//
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
// and JSON codecs. They also support gzip compression.
func NewGreetServiceHandler(svc GreetServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
greetServiceMethods := v1.File_greet_v1_greet_proto.Services().ByName("GreetService").Methods()
greetServiceGreetHandler := connect.NewUnaryHandler(
GreetServiceGreetProcedure,
svc.Greet,
connect.WithSchema(greetServiceMethods.ByName("Greet")),
connect.WithHandlerOptions(opts...),
)
return "/greet.v1.GreetService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.Path)
switch r.URL.Path {
case GreetServiceGreetProcedure:
greetServiceGreetHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
// UnimplementedGreetServiceHandler returns CodeUnimplemented from all methods.
type UnimplementedGreetServiceHandler struct{}
func (UnimplementedGreetServiceHandler) Greet(context.Context, *connect.Request[v1.GreetRequest]) (*connect.Response[v1.GreetResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("greet.v1.GreetService.Greet is not implemented"))
}然后我们定义server代码
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)
if err := http.ListenAndServe(
"localhost:8083",
// Use h2c so we can serve HTTP/2 without TLS.
h2c.NewHandler(mux, &http2.Server{}),
); err != nil {
fmt.Println(err)
}
}然后我们测试下:发现http协议能走通
curl \
--header "Content-Type: application/json" \
-H "Accept: application/json" \
--data '{"name": "Jane"}' \
http://localhost:8083/greet.v1.GreetService/Greet
{"greeting":"Hello, Jane!"}接着我们试试grpc协议,发现也是可以走通的
grpcurl \
-protoset <(buf build -o -) -plaintext \
-d '{"name": "Jane"}' \
localhost:8083 greet.v1.GreetService/Greet
{
"greeting": "Hello, Jane!"
}测试完毕后,我们开始golang代码发起调用,先看http协议的
package main
import (
"context"
"log"
"net/http"
greetv1 "example/gen/greet/v1"
"example/gen/greet/v1/greetv1connect"
"connectrpc.com/connect"
)
func main() {
client := greetv1connect.NewGreetServiceClient(
http.DefaultClient,
"http://localhost:8083",
)
res, err := client.Greet(
context.Background(),
connect.NewRequest(&greetv1.GreetRequest{Name: "Jane"}),
)
if err != nil {
log.Println(err)
return
}
log.Println(res.Msg.Greeting)
}go run ./cmd/client/main.go
2025/05/29 14:47:18 Hello, Jane!发现成功了,那我们试试grpc协议的
package main
import (
"context"
"log"
"net/http"
greetv1 "example/gen/greet/v1"
"example/gen/greet/v1/greetv1connect"
"connectrpc.com/connect"
)
func main() {
client := greetv1connect.NewGreetServiceClient(
http.DefaultClient,
"http://localhost:8083",
connect.WithGRPC(),
)
res, err := client.Greet(
context.Background(),
connect.NewRequest(&greetv1.GreetRequest{Name: "Jane"}),
)
if err != nil {
log.Println(err)
return
}
log.Println(res.Msg.Greeting)
}% go run ./cmd/client/grpc/main.go
2025/05/29 14:47:29 Hello, Jane!同样也能尝试成功。
本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!