gRPC+gRPC Gateway 能不能不用证书?

若是你之前有涉猎过 gRPC+gRPC Gateway 这两个组件,你确定会遇到这个问题,就是 “为何非得开 TLS,才可以实现同端口双流量,能不能不开?” 又或是 “我不想用证书就实现这些功能,行不行?”。我被无数的人问过无数次这些问题,也说服过不少人,但说服归说服,不表明放弃。前年不行,不表明今年不行,在今天我但愿分享前因后果和具体的实现方式给你。html

image

原文地址:gRPC+gRPC Gateway 能不能不用证书?git

过去

为何 h2 不行

由于 net/http2 仅支持 "h2" 标识,而 "h2" 标识 HTTP/2 必须使用传输层安全性(TLS)的协议,此标识符用于 TLS 应用层协议协商字段以及识别 HTTP/2 over TLS。github

简单来说,也就 net/http2 必须使用 TLS 来交互。通俗来说就要用证书,那么理所固然,也就没法支持非 TLS 的状况了。golang

寻找 h2c

那这条路不行,咱们再想一想别的路?那就是 HTTP/2 规范中的 "h2c" 标识了,"h2c" 标识容许经过明文 TCP 运行 HTTP/2 的协议,此标识符用于 HTTP/1.1 升级标头字段以及标识 HTTP/2 over TCP。安全

可是这条路,早在 2015 年就已经有在 issue 中进行讨论,当时 @bradfitz 明确表示 “不打算支持 h2c,对仅支持 TLS 的状况很是满意,一年后再问我一次”,原文回复以下:app

We do not plan to support h2c. I don't want to receive bug reports from users who get bitten by transparent proxies messing with h2c. Also, until there's widespread browser support, it's not interesting. I am also not interested in being the chicken or the egg to get browser support going. I'm very happy with the TLS-only situation, and things like https://LetsEncrypt.org/ will make TLS much easier (and automatic) soon.

Ask me again in one year.curl

琢磨其余方式

使用 cmux

基于多路复用器 soheilhy/cmux 的另类实现 Stoakes/grpc-gateway-example。若对 cmux 的实现方式感兴趣,还能够看看 《Golang: Run multiple services on one port》ide

使用第三方 h2

这种属于本身实现了 h2c 的逻辑,以此达到效果。post

如今

通过社区的不断讨论,最后在 2018 年 6 月,表明 "h2c" 标志的 golang.org/x/net/http2/h2c 标准库正式合并进来,自此咱们就可使用官方标准库(h2c),这个标准库实现了 HTTP/2 的未加密模式,所以咱们就能够利用该标准库在同个端口上既提供 HTTP/1.1 又提供 HTTP/2 的功能了。google

使用标准库 h2c

import (
    ...

    "golang.org/x/net/http2"
    "golang.org/x/net/http2/h2c"
    "google.golang.org/grpc"

    "github.com/grpc-ecosystem/grpc-gateway/runtime"

    pb "github.com/EDDYCJY/go-grpc-example/proto"
)

...

func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
    return 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 {
            otherHandler.ServeHTTP(w, r)
        }
    }), &http2.Server{})
}

func main() {
    server := grpc.NewServer()

    pb.RegisterSearchServiceServer(server, &SearchService{})

    mux := http.NewServeMux()
    gwmux := runtime.NewServeMux()
    dopts := []grpc.DialOption{grpc.WithInsecure()}

    err := pb.RegisterSearchServiceHandlerFromEndpoint(context.Background(), gwmux, "localhost:"+PORT, dopts)
    ...
    mux.Handle("/", gwmux)
    http.ListenAndServe(":"+PORT, grpcHandlerFunc(server, mux))
}

咱们能够看到关键之处在于调用了 h2c.NewHandler 方法进行了特殊处理,h2c.NewHandler 会返回一个 http.handler,主要的内部逻辑是拦截了全部 h2c 流量,而后根据不一样的请求流量类型将其劫持并重定向到相应的 Hander 中去处理。

验证

HTTP/1.1

$ curl -X GET 'http://127.0.0.1:9005/search?request=EDDYCJY'
{"response":"EDDYCJY"}

HTTP/2(gRPC)

...
func main() {
    conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())
    ...
    client := pb.NewSearchServiceClient(conn)
    resp, err := client.Search(context.Background(), &pb.SearchRequest{
        Request: "gRPC",
    })
}

输出结果:

$ go run main.go
2019/06/21 20:04:09 resp: gRPC h2c Server

总结

在本文中我介绍了大体的来龙去脉,且介绍了几种解决方法,我建议你选择官方的 h2c 标准库去实现这个功能,也简单。在最后,无论你是否曾经为这个问题烦恼过许久,又或者正在纠结,都但愿这篇文章可以帮到你。

参考

相关文章
相关标签/搜索