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

golang源码分析:connectrpc/connect-go原理

作者头像
golangLeetcode
发布2026-03-18 17:42:53
发布2026-03-18 17:42:53
1250
举报

在介绍完connectrpc/connect-go的使用之后golang源码分析:connectrpc/connect-go,我们分析下它实现端口复用的原理:grpc是基于http2的,本质上也是http协议,只不过是二机制的,那么如何区分是普通的http协议还是grpc协议呢?grpc在传输的过程中会加一个http头Content-Type:application/grpc,如果我们能识别这个头,那么就可以在应用层进行分流,实现http协议和grpc协议共用一个端口。

下面就是我们自己实现的一个server,可以看到和直接实现http server几乎没有差异,只是在定义handler的时候,需要使用h2c.NewHandler来封装下,用明文传输http2的内容,否则拿不到gpc的header,就无法分流。无论是grpc server还是http server,他们的handler都是实现了

代码语言:javascript
复制
ServeHTTP(w http.ResponseWriter, r *http.Request)

接口的一个函数我们可以在两个地方分流,如下面例子所示,第一个是在定义HandleFunc的时候,我们可以指定不同的实现。第二个地方就是在ServerHTTP函数内部分流,在它内部分别调用两个协议的ServeHTTP接口。

代码语言:javascript
复制
package main
import (
    "context"
    "fmt"
    "log"
    "net/http"
    "strings"

    "golang.org/x/net/http2"
    "golang.org/x/net/http2/h2c"
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    "google.golang.org/grpc/reflection"
    pb "learn/langchain/protoc_gen_mcp/exp2/gen/go/example/v1"
)
type server struct {
    pb.UnimplementedExampleServiceServer
    grpcServer *grpc.Server
}
func (s *server) CreateExample(ctx context.Context, in *pb.CreateExampleRequest) (*pb.CreateExampleResponse, error) {
    log.Printf("Received: %v", in)
    return &pb.CreateExampleResponse{
        SomeString: "Hello, world!",
    }, nil
}
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.ProtoMajor == 2  strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
        fmt.Println("run grpc server")
        s.grpcServer.ServeHTTP(w, r)
    } else {
        httpMux := http.NewServeMux()
        httpMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("HTTP handler"))
        })
        httpMux.ServeHTTP(w, r)
    }
}
func main() {
    grpcServer := grpc.NewServer()
    s := &server{
        grpcServer: grpcServer,
    }
    pb.RegisterExampleServiceServer(grpcServer, s)
    reflection.Register(grpcServer)
    srv := &http.Server{
        Addr: ":8089",
        Handler: h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
                grpcServer.ServeHTTP(w, r)
            } else {
                grpcServer.ServeHTTP(w, r)
            }
        }), &http2.Server{}),
    }
    log.Println("Server listening on :8089")
    log.Fatal(srv.ListenAndServe())
}

启动server后我们来测试下,首先是grpc协议

代码语言:javascript
复制
grpcurl -plaintext localhost:8089 list
example.v1.ExampleService
grpc.reflection.v1.ServerReflection
grpc.reflection.v1alpha.ServerReflection 
代码语言:javascript
复制
grpcurl -plaintext \
    -protoset <(buf build -o -) \
    -d '{"some_string": "Jane"}' \
    localhost:8089 example.v1.ExampleService.CreateExample
{
  "someString": "Hello, world!"
}

然后我们试试http协议

代码语言:javascript
复制
http://localhost:8089
HTTP handler

测试下通过grpc client调用

代码语言:javascript
复制
package main
import (
    "context"
    "fmt"
    "log"

    pb "learn/langchain/protoc_gen_mcp/exp2/gen/go/example/v1"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "google.golang.org/grpc/metadata"
)
func main() {
    conn, err := grpc.Dial("localhost:8089",
        grpc.WithDefaultCallOptions(
            grpc.Header(&metadata.MD{
                // 在 gRPC 中,Content-Type 通常是 application/grpc,但这个值是由 gRPC 自动设置的
                "Content-Type": []string{"application/grpc"},
            },
            ),
        ),
        grpc.WithInsecure(),
        grpc.WithTransportCredentials(insecure.NewCredentials()),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    // 这里调用gRPC服务方法
    client := pb.NewExampleServiceClient(conn)
    md := metadata.Pairs("Content-Type", "application/grpc")
    ctx := metadata.NewOutgoingContext(context.Background(), md)
    res, err := client.CreateExample(ctx, &pb.CreateExampleRequest{
        SomeInt32:  123,
        SomeInt64:  456,
        SomeString: "hello world",
    })
    fmt.Println(res.GetSomeString())
}

测试下

代码语言:javascript
复制
go run ./client/main.go
Hello, world!

connectrpc/connect-go其实就是利用上述原理,通过protoc的插件生成了Server端和Client端的初始化方法。

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

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

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

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

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