mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	api: add undocumented /grpc endpoint to talk to GRPC services
Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
		
							parent
							
								
									32157f9b12
								
							
						
					
					
						commit
						e8382ece65
					
				
					 7 changed files with 106 additions and 20 deletions
				
			
		
							
								
								
									
										8
									
								
								api/server/router/grpc/backend.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								api/server/router/grpc/backend.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								api/server/router/grpc/grpc.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								api/server/router/grpc/grpc.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								api/server/router/grpc/grpc_routes.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								api/server/router/grpc/grpc_routes.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue