Signed-off-by: Tibor Vass <tibor@docker.com>
| 0 | 8 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,37 @@ |
| 0 |
+package grpc // import "github.com/docker/docker/api/server/router/grpc" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/api/server/router" |
|
| 4 |
+ "golang.org/x/net/http2" |
|
| 5 |
+ "google.golang.org/grpc" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+type grpcRouter struct {
|
|
| 9 |
+ routes []router.Route |
|
| 10 |
+ grpcServer *grpc.Server |
|
| 11 |
+ h2Server *http2.Server |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+// NewRouter initializes a new grpc http router |
|
| 15 |
+func NewRouter(backends ...Backend) router.Router {
|
|
| 16 |
+ r := &grpcRouter{
|
|
| 17 |
+ h2Server: &http2.Server{},
|
|
| 18 |
+ grpcServer: grpc.NewServer(), |
|
| 19 |
+ } |
|
| 20 |
+ for _, b := range backends {
|
|
| 21 |
+ b.RegisterGRPC(r.grpcServer) |
|
| 22 |
+ } |
|
| 23 |
+ r.initRoutes() |
|
| 24 |
+ return r |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+// Routes returns the available routers to the session controller |
|
| 28 |
+func (r *grpcRouter) Routes() []router.Route {
|
|
| 29 |
+ return r.routes |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func (r *grpcRouter) initRoutes() {
|
|
| 33 |
+ r.routes = []router.Route{
|
|
| 34 |
+ router.NewPostRoute("/grpc", r.serveGRPC),
|
|
| 35 |
+ } |
|
| 36 |
+} |
| 0 | 37 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,45 @@ |
| 0 |
+package grpc // import "github.com/docker/docker/api/server/router/grpc" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "net/http" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/pkg/errors" |
|
| 7 |
+ "golang.org/x/net/http2" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func (gr *grpcRouter) serveGRPC(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 11 |
+ h, ok := w.(http.Hijacker) |
|
| 12 |
+ if !ok {
|
|
| 13 |
+ return errors.New("handler does not support hijack")
|
|
| 14 |
+ } |
|
| 15 |
+ proto := r.Header.Get("Upgrade")
|
|
| 16 |
+ if proto == "" {
|
|
| 17 |
+ return errors.New("no upgrade proto in request")
|
|
| 18 |
+ } |
|
| 19 |
+ if proto != "h2c" {
|
|
| 20 |
+ return errors.Errorf("protocol %s not supported", proto)
|
|
| 21 |
+ } |
|
| 22 |
+ |
|
| 23 |
+ conn, _, err := h.Hijack() |
|
| 24 |
+ if err != nil {
|
|
| 25 |
+ return err |
|
| 26 |
+ } |
|
| 27 |
+ resp := &http.Response{
|
|
| 28 |
+ StatusCode: http.StatusSwitchingProtocols, |
|
| 29 |
+ ProtoMajor: 1, |
|
| 30 |
+ ProtoMinor: 1, |
|
| 31 |
+ Header: http.Header{},
|
|
| 32 |
+ } |
|
| 33 |
+ resp.Header.Set("Connection", "Upgrade")
|
|
| 34 |
+ resp.Header.Set("Upgrade", proto)
|
|
| 35 |
+ |
|
| 36 |
+ // set raw mode |
|
| 37 |
+ conn.Write([]byte{})
|
|
| 38 |
+ resp.Write(conn) |
|
| 39 |
+ |
|
| 40 |
+ // https://godoc.org/golang.org/x/net/http2#Server.ServeConn |
|
| 41 |
+ // TODO: is it a problem that conn has already been written to? |
|
| 42 |
+ gr.h2Server.ServeConn(conn, &http2.ServeConnOpts{Handler: gr.grpcServer})
|
|
| 43 |
+ return nil |
|
| 44 |
+} |
| ... | ... |
@@ -38,6 +38,17 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu |
| 38 | 38 |
return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
|
| 39 | 39 |
} |
| 40 | 40 |
|
| 41 |
+// DialHijack returns a hijacked connection with negotiated protocol proto. |
|
| 42 |
+func (cli *Client) DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) {
|
|
| 43 |
+ req, err := http.NewRequest("POST", url, nil)
|
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ return nil, err |
|
| 46 |
+ } |
|
| 47 |
+ req = cli.addHeaders(req, meta) |
|
| 48 |
+ |
|
| 49 |
+ return cli.setupHijackConn(ctx, req, proto) |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 41 | 52 |
// fallbackDial is used when WithDialer() was not called. |
| 42 | 53 |
// See cli.Dialer(). |
| 43 | 54 |
func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
| ... | ... |
@@ -38,7 +38,7 @@ type CommonAPIClient interface {
|
| 38 | 38 |
ServerVersion(ctx context.Context) (types.Version, error) |
| 39 | 39 |
NegotiateAPIVersion(ctx context.Context) |
| 40 | 40 |
NegotiateAPIVersionPing(types.Ping) |
| 41 |
- DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) |
|
| 41 |
+ DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) |
|
| 42 | 42 |
Dialer() func(context.Context) (net.Conn, error) |
| 43 | 43 |
Close() error |
| 44 | 44 |
} |
| 45 | 45 |
deleted file mode 100644 |
| ... | ... |
@@ -1,18 +0,0 @@ |
| 1 |
-package client // import "github.com/docker/docker/client" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "context" |
|
| 5 |
- "net" |
|
| 6 |
- "net/http" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// DialSession returns a connection that can be used communication with daemon |
|
| 10 |
-func (cli *Client) DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
|
| 11 |
- req, err := http.NewRequest("POST", "/session", nil)
|
|
| 12 |
- if err != nil {
|
|
| 13 |
- return nil, err |
|
| 14 |
- } |
|
| 15 |
- req = cli.addHeaders(req, meta) |
|
| 16 |
- |
|
| 17 |
- return cli.setupHijackConn(ctx, req, proto) |
|
| 18 |
-} |
| ... | ... |
@@ -3,6 +3,7 @@ package build |
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"io/ioutil" |
| 6 |
+ "net" |
|
| 6 | 7 |
"net/http" |
| 7 | 8 |
"strings" |
| 8 | 9 |
"testing" |
| ... | ... |
@@ -109,7 +110,9 @@ func testBuildWithSession(t *testing.T, client dclient.APIClient, daemonHost str |
| 109 | 109 |
g, ctx := errgroup.WithContext(ctx) |
| 110 | 110 |
|
| 111 | 111 |
g.Go(func() error {
|
| 112 |
- return sess.Run(ctx, client.DialSession) |
|
| 112 |
+ return sess.Run(ctx, func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
|
| 113 |
+ return client.DialHijack(ctx, "/session", "h2c", meta) |
|
| 114 |
+ }) |
|
| 113 | 115 |
}) |
| 114 | 116 |
|
| 115 | 117 |
g.Go(func() error {
|