首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >golang源码分析:connectrpc/connect-go

golang源码分析:connectrpc/connect-go

作者头像
golangLeetcode
发布2026-03-18 17:41:21
发布2026-03-18 17:41:21
760
举报

如果我们要起一个同时支持grpc和http协议的服务,以前的做法是使用grpc-gateway,这种做法除了引入额外组件更麻烦外,还多了一层rpc调用,带来了性能上的损耗。其实grpc是基于http协议传输的,因此我们没有必要在传输层分流,在应用层分流的话,就可以做到同一个服务里既支持grpc协议,又支持http协议。connectrpc就是一个不错的选择,其仓库位于:http://github.com/connectrpc/connect-go

下面我们分析下代码,看看如何使用,首先需要初始化项目

代码语言:javascript
复制
mkdir connect-go-example
cd connect-go-example
go mod init example

安装依赖的工具,这里主要是引入了protoc-gen-connect-go 的protoc插件,可以辅助生成connectrpc代码

代码语言:javascript
复制
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

代码语言:javascript
复制
mkdir -p greet/v1
touch greet/v1/greet.proto
代码语言:javascript
复制
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项目

代码语言:javascript
复制
% buf config init

配置buf生成工具,需要本地gen-go插件和gen-connect-go插件

代码语言:javascript
复制
version: v2
plugins:
  - local: protoc-gen-go
    out: gen
    opt: paths=source_relative
  - local: protoc-gen-connect-go
    out: gen
    opt: paths=source_relative

执行代码生成

代码语言:javascript
复制
buf lint
buf generate

生成后源码结构如下

代码语言:javascript
复制
|____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的源码接口。

代码语言:javascript
复制
// 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代码

代码语言:javascript
复制
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协议能走通

代码语言:javascript
复制
curl \
    --header "Content-Type: application/json" \
    -H "Accept: application/json" \
    --data '{"name": "Jane"}' \
    http://localhost:8083/greet.v1.GreetService/Greet
{"greeting":"Hello, Jane!"}

接着我们试试grpc协议,发现也是可以走通的

代码语言:javascript
复制
grpcurl \
    -protoset <(buf build -o -) -plaintext \
    -d '{"name": "Jane"}' \
    localhost:8083 greet.v1.GreetService/Greet
{
  "greeting": "Hello, Jane!"
}

测试完毕后,我们开始golang代码发起调用,先看http协议的

代码语言:javascript
复制
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)
}
代码语言:javascript
复制
go run ./cmd/client/main.go
2025/05/29 14:47:18 Hello, Jane!

发现成功了,那我们试试grpc协议的

代码语言:javascript
复制
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)
}
代码语言:javascript
复制
% go run ./cmd/client/grpc/main.go
2025/05/29 14:47:29 Hello, Jane!

同样也能尝试成功。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-05-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档