
在介绍完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都是实现了
ServeHTTP(w http.ResponseWriter, r *http.Request)接口的一个函数我们可以在两个地方分流,如下面例子所示,第一个是在定义HandleFunc的时候,我们可以指定不同的实现。第二个地方就是在ServerHTTP函数内部分流,在它内部分别调用两个协议的ServeHTTP接口。
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协议
grpcurl -plaintext localhost:8089 list
example.v1.ExampleService
grpc.reflection.v1.ServerReflection
grpc.reflection.v1alpha.ServerReflection grpcurl -plaintext \
-protoset <(buf build -o -) \
-d '{"some_string": "Jane"}' \
localhost:8089 example.v1.ExampleService.CreateExample
{
"someString": "Hello, world!"
}然后我们试试http协议
http://localhost:8089
HTTP handler测试下通过grpc client调用
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())
}测试下
go run ./client/main.go
Hello, world!connectrpc/connect-go其实就是利用上述原理,通过protoc的插件生成了Server端和Client端的初始化方法。
本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!