Explorar el Código

api: add undocumented /grpc endpoint to talk to GRPC services

Signed-off-by: Tibor Vass <tibor@docker.com>
Tibor Vass hace 6 años
padre
commit
e8382ece65

+ 8 - 0
api/server/router/grpc/backend.go

@@ -0,0 +1,8 @@
+package grpc // import "github.com/docker/docker/api/server/router/grpc"
+
+import "google.golang.org/grpc"
+
+// Backend abstracts a registerable GRPC service.
+type Backend interface {
+	RegisterGRPC(*grpc.Server)
+}

+ 37 - 0
api/server/router/grpc/grpc.go

@@ -0,0 +1,37 @@
+package grpc // import "github.com/docker/docker/api/server/router/grpc"
+
+import (
+	"github.com/docker/docker/api/server/router"
+	"golang.org/x/net/http2"
+	"google.golang.org/grpc"
+)
+
+type grpcRouter struct {
+	routes     []router.Route
+	grpcServer *grpc.Server
+	h2Server   *http2.Server
+}
+
+// NewRouter initializes a new grpc http router
+func NewRouter(backends ...Backend) router.Router {
+	r := &grpcRouter{
+		h2Server:   &http2.Server{},
+		grpcServer: grpc.NewServer(),
+	}
+	for _, b := range backends {
+		b.RegisterGRPC(r.grpcServer)
+	}
+	r.initRoutes()
+	return r
+}
+
+// Routes returns the available routers to the session controller
+func (r *grpcRouter) Routes() []router.Route {
+	return r.routes
+}
+
+func (r *grpcRouter) initRoutes() {
+	r.routes = []router.Route{
+		router.NewPostRoute("/grpc", r.serveGRPC),
+	}
+}

+ 45 - 0
api/server/router/grpc/grpc_routes.go

@@ -0,0 +1,45 @@
+package grpc // import "github.com/docker/docker/api/server/router/grpc"
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/pkg/errors"
+	"golang.org/x/net/http2"
+)
+
+func (gr *grpcRouter) serveGRPC(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	h, ok := w.(http.Hijacker)
+	if !ok {
+		return errors.New("handler does not support hijack")
+	}
+	proto := r.Header.Get("Upgrade")
+	if proto == "" {
+		return errors.New("no upgrade proto in request")
+	}
+	if proto != "h2c" {
+		return errors.Errorf("protocol %s not supported", proto)
+	}
+
+	conn, _, err := h.Hijack()
+	if err != nil {
+		return err
+	}
+	resp := &http.Response{
+		StatusCode: http.StatusSwitchingProtocols,
+		ProtoMajor: 1,
+		ProtoMinor: 1,
+		Header:     http.Header{},
+	}
+	resp.Header.Set("Connection", "Upgrade")
+	resp.Header.Set("Upgrade", proto)
+
+	// set raw mode
+	conn.Write([]byte{})
+	resp.Write(conn)
+
+	// https://godoc.org/golang.org/x/net/http2#Server.ServeConn
+	// TODO: is it a problem that conn has already been written to?
+	gr.h2Server.ServeConn(conn, &http2.ServeConnOpts{Handler: gr.grpcServer})
+	return nil
+}

+ 11 - 0
client/hijack.go

@@ -38,6 +38,17 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
 	return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
 }
 
+// DialHijack returns a hijacked connection with negotiated protocol proto.
+func (cli *Client) DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) {
+	req, err := http.NewRequest("POST", url, nil)
+	if err != nil {
+		return nil, err
+	}
+	req = cli.addHeaders(req, meta)
+
+	return cli.setupHijackConn(ctx, req, proto)
+}
+
 // fallbackDial is used when WithDialer() was not called.
 // See cli.Dialer().
 func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {

+ 1 - 1
client/interface.go

@@ -38,7 +38,7 @@ type CommonAPIClient interface {
 	ServerVersion(ctx context.Context) (types.Version, error)
 	NegotiateAPIVersion(ctx context.Context)
 	NegotiateAPIVersionPing(types.Ping)
-	DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error)
+	DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error)
 	Dialer() func(context.Context) (net.Conn, error)
 	Close() error
 }

+ 0 - 18
client/session.go

@@ -1,18 +0,0 @@
-package client // import "github.com/docker/docker/client"
-
-import (
-	"context"
-	"net"
-	"net/http"
-)
-
-// DialSession returns a connection that can be used communication with daemon
-func (cli *Client) DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
-	req, err := http.NewRequest("POST", "/session", nil)
-	if err != nil {
-		return nil, err
-	}
-	req = cli.addHeaders(req, meta)
-
-	return cli.setupHijackConn(ctx, req, proto)
-}

+ 4 - 1
integration/build/build_session_test.go

@@ -3,6 +3,7 @@ package build
 import (
 	"context"
 	"io/ioutil"
+	"net"
 	"net/http"
 	"strings"
 	"testing"
@@ -109,7 +110,9 @@ func testBuildWithSession(t *testing.T, client dclient.APIClient, daemonHost str
 	g, ctx := errgroup.WithContext(ctx)
 
 	g.Go(func() error {
-		return sess.Run(ctx, client.DialSession)
+		return sess.Run(ctx, func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
+			return client.DialHijack(ctx, "/session", "h2c", meta)
+		})
 	})
 
 	g.Go(func() error {