LoginSignup
2
4

More than 5 years have passed since last update.

ひとつのポートで gRPC と HTTP を同時に受けるには

Last updated at Posted at 2017-04-15

ひとつのポートで gRPC と HTTP を同時に受けるには

次のように grpc っぽいリクエストを grpc.Server.ServeHTTP へ分岐させる http.Handler を書き、 TLS を有効にして Listen させれば良い。(参考)

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

Listenするコードは次のとおり。

func main() {
    opts := []grpc.ServerOption{
        grpc.Creds(credentials.NewClientTLSFromCert(cert.CertPool, "localhost"))}

    g := grpc.NewServer(opts...)
    pb.RegisterSKVSServer(g, &srv{data: map[string]string{}})

    h := http.NewServeMux()
    h.Handle("/", http.HandlerFunc(hello))

    s := &http.Server{
        Addr: ":8080",
        Handler: grpcHandlerFunc(g, h),
        TLSConfig: &tls.Config{
            Certificates: []tls.Certificate{*cert.KeyPair},
            NextProtos: []string{"h2"},
        },
    }

    conn, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }

    log.Println("Listening...")
    s.Serve(tls.NewListener(conn, s.TLSConfig))
}
  • ここで pb は適当な proto から生成したパッケージである

注意点

  • この方法は、grpc.Server.Serve() を使う場合よりもパフォーマンスが悪い。いずれ改善されるだろうが、優先度は高くないようだ(参考)
    • パフォーマンスが必要な場合、 cmux で振り分けたあと grpc.Serve.Serve() を使うことで改善できそうだが、これはChromeでアクセスしたときにうまく動作しないらしい。 結局パフォーマンスが必要なら http 系とはポートを分けろ、というのが現在の結論のようだ(参考)
  • TLSを有効にしないと、 http.Server が自身の Handler を呼び出す前にリクエストを弾いてしまうようだ。こちらも将来的にTLSが不要になるよう対処される可能性はあるが、現在のところ優先度は低いようだ(参考)
2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4