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
|
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.
|
// fallbackDial is used when WithDialer() was not called.
|
||||||
// See cli.Dialer().
|
// See cli.Dialer().
|
||||||
func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
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)
|
ServerVersion(ctx context.Context) (types.Version, error)
|
||||||
NegotiateAPIVersion(ctx context.Context)
|
NegotiateAPIVersion(ctx context.Context)
|
||||||
NegotiateAPIVersionPing(types.Ping)
|
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)
|
Dialer() func(context.Context) (net.Conn, error)
|
||||||
Close() 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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -109,7 +110,9 @@ func testBuildWithSession(t *testing.T, client dclient.APIClient, daemonHost str
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
g.Go(func() error {
|
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 {
|
g.Go(func() error {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue