summaryrefslogtreecommitdiff
path: root/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy')
-rw-r--r--vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go145
-rw-r--r--vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go326
-rw-r--r--vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go107
3 files changed, 578 insertions, 0 deletions
diff --git a/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go
new file mode 100644
index 000000000..3dc8e23ae
--- /dev/null
+++ b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go
@@ -0,0 +1,145 @@
+/*
+Copyright 2015 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package spdy
+
+import (
+ "net"
+ "net/http"
+ "sync"
+ "time"
+
+ "github.com/docker/spdystream"
+ "github.com/golang/glog"
+ "k8s.io/apimachinery/pkg/util/httpstream"
+)
+
+// connection maintains state about a spdystream.Connection and its associated
+// streams.
+type connection struct {
+ conn *spdystream.Connection
+ streams []httpstream.Stream
+ streamLock sync.Mutex
+ newStreamHandler httpstream.NewStreamHandler
+}
+
+// NewClientConnection creates a new SPDY client connection.
+func NewClientConnection(conn net.Conn) (httpstream.Connection, error) {
+ spdyConn, err := spdystream.NewConnection(conn, false)
+ if err != nil {
+ defer conn.Close()
+ return nil, err
+ }
+
+ return newConnection(spdyConn, httpstream.NoOpNewStreamHandler), nil
+}
+
+// NewServerConnection creates a new SPDY server connection. newStreamHandler
+// will be invoked when the server receives a newly created stream from the
+// client.
+func NewServerConnection(conn net.Conn, newStreamHandler httpstream.NewStreamHandler) (httpstream.Connection, error) {
+ spdyConn, err := spdystream.NewConnection(conn, true)
+ if err != nil {
+ defer conn.Close()
+ return nil, err
+ }
+
+ return newConnection(spdyConn, newStreamHandler), nil
+}
+
+// newConnection returns a new connection wrapping conn. newStreamHandler
+// will be invoked when the server receives a newly created stream from the
+// client.
+func newConnection(conn *spdystream.Connection, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection {
+ c := &connection{conn: conn, newStreamHandler: newStreamHandler}
+ go conn.Serve(c.newSpdyStream)
+ return c
+}
+
+// createStreamResponseTimeout indicates how long to wait for the other side to
+// acknowledge the new stream before timing out.
+const createStreamResponseTimeout = 30 * time.Second
+
+// Close first sends a reset for all of the connection's streams, and then
+// closes the underlying spdystream.Connection.
+func (c *connection) Close() error {
+ c.streamLock.Lock()
+ for _, s := range c.streams {
+ // calling Reset instead of Close ensures that all streams are fully torn down
+ s.Reset()
+ }
+ c.streams = make([]httpstream.Stream, 0)
+ c.streamLock.Unlock()
+
+ // now that all streams are fully torn down, it's safe to call close on the underlying connection,
+ // which should be able to terminate immediately at this point, instead of waiting for any
+ // remaining graceful stream termination.
+ return c.conn.Close()
+}
+
+// CreateStream creates a new stream with the specified headers and registers
+// it with the connection.
+func (c *connection) CreateStream(headers http.Header) (httpstream.Stream, error) {
+ stream, err := c.conn.CreateStream(headers, nil, false)
+ if err != nil {
+ return nil, err
+ }
+ if err = stream.WaitTimeout(createStreamResponseTimeout); err != nil {
+ return nil, err
+ }
+
+ c.registerStream(stream)
+ return stream, nil
+}
+
+// registerStream adds the stream s to the connection's list of streams that
+// it owns.
+func (c *connection) registerStream(s httpstream.Stream) {
+ c.streamLock.Lock()
+ c.streams = append(c.streams, s)
+ c.streamLock.Unlock()
+}
+
+// CloseChan returns a channel that, when closed, indicates that the underlying
+// spdystream.Connection has been closed.
+func (c *connection) CloseChan() <-chan bool {
+ return c.conn.CloseChan()
+}
+
+// newSpdyStream is the internal new stream handler used by spdystream.Connection.Serve.
+// It calls connection's newStreamHandler, giving it the opportunity to accept or reject
+// the stream. If newStreamHandler returns an error, the stream is rejected. If not, the
+// stream is accepted and registered with the connection.
+func (c *connection) newSpdyStream(stream *spdystream.Stream) {
+ replySent := make(chan struct{})
+ err := c.newStreamHandler(stream, replySent)
+ rejectStream := (err != nil)
+ if rejectStream {
+ glog.Warningf("Stream rejected: %v", err)
+ stream.Reset()
+ return
+ }
+
+ c.registerStream(stream)
+ stream.SendReply(http.Header{}, rejectStream)
+ close(replySent)
+}
+
+// SetIdleTimeout sets the amount of time the connection may remain idle before
+// it is automatically closed.
+func (c *connection) SetIdleTimeout(timeout time.Duration) {
+ c.conn.SetIdleTimeout(timeout)
+}
diff --git a/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go
new file mode 100644
index 000000000..12bef075d
--- /dev/null
+++ b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go
@@ -0,0 +1,326 @@
+/*
+Copyright 2015 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package spdy
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/tls"
+ "encoding/base64"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "strings"
+
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/serializer"
+ "k8s.io/apimachinery/pkg/util/httpstream"
+ utilnet "k8s.io/apimachinery/pkg/util/net"
+ "k8s.io/apimachinery/third_party/forked/golang/netutil"
+)
+
+// SpdyRoundTripper knows how to upgrade an HTTP request to one that supports
+// multiplexed streams. After RoundTrip() is invoked, Conn will be set
+// and usable. SpdyRoundTripper implements the UpgradeRoundTripper interface.
+type SpdyRoundTripper struct {
+ //tlsConfig holds the TLS configuration settings to use when connecting
+ //to the remote server.
+ tlsConfig *tls.Config
+
+ /* TODO according to http://golang.org/pkg/net/http/#RoundTripper, a RoundTripper
+ must be safe for use by multiple concurrent goroutines. If this is absolutely
+ necessary, we could keep a map from http.Request to net.Conn. In practice,
+ a client will create an http.Client, set the transport to a new insteace of
+ SpdyRoundTripper, and use it a single time, so this hopefully won't be an issue.
+ */
+ // conn is the underlying network connection to the remote server.
+ conn net.Conn
+
+ // Dialer is the dialer used to connect. Used if non-nil.
+ Dialer *net.Dialer
+
+ // proxier knows which proxy to use given a request, defaults to http.ProxyFromEnvironment
+ // Used primarily for mocking the proxy discovery in tests.
+ proxier func(req *http.Request) (*url.URL, error)
+
+ // followRedirects indicates if the round tripper should examine responses for redirects and
+ // follow them.
+ followRedirects bool
+}
+
+var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{}
+var _ httpstream.UpgradeRoundTripper = &SpdyRoundTripper{}
+var _ utilnet.Dialer = &SpdyRoundTripper{}
+
+// NewRoundTripper creates a new SpdyRoundTripper that will use
+// the specified tlsConfig.
+func NewRoundTripper(tlsConfig *tls.Config, followRedirects bool) httpstream.UpgradeRoundTripper {
+ return NewSpdyRoundTripper(tlsConfig, followRedirects)
+}
+
+// NewSpdyRoundTripper creates a new SpdyRoundTripper that will use
+// the specified tlsConfig. This function is mostly meant for unit tests.
+func NewSpdyRoundTripper(tlsConfig *tls.Config, followRedirects bool) *SpdyRoundTripper {
+ return &SpdyRoundTripper{tlsConfig: tlsConfig, followRedirects: followRedirects}
+}
+
+// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during
+// proxying with a spdy roundtripper.
+func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config {
+ return s.tlsConfig
+}
+
+// Dial implements k8s.io/apimachinery/pkg/util/net.Dialer.
+func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) {
+ conn, err := s.dial(req)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := req.Write(conn); err != nil {
+ conn.Close()
+ return nil, err
+ }
+
+ return conn, nil
+}
+
+// dial dials the host specified by req, using TLS if appropriate, optionally
+// using a proxy server if one is configured via environment variables.
+func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) {
+ proxier := s.proxier
+ if proxier == nil {
+ proxier = http.ProxyFromEnvironment
+ }
+ proxyURL, err := proxier(req)
+ if err != nil {
+ return nil, err
+ }
+
+ if proxyURL == nil {
+ return s.dialWithoutProxy(req.URL)
+ }
+
+ // ensure we use a canonical host with proxyReq
+ targetHost := netutil.CanonicalAddr(req.URL)
+
+ // proxying logic adapted from http://blog.h6t.eu/post/74098062923/golang-websocket-with-http-proxy-support
+ proxyReq := http.Request{
+ Method: "CONNECT",
+ URL: &url.URL{},
+ Host: targetHost,
+ }
+
+ if pa := s.proxyAuth(proxyURL); pa != "" {
+ proxyReq.Header = http.Header{}
+ proxyReq.Header.Set("Proxy-Authorization", pa)
+ }
+
+ proxyDialConn, err := s.dialWithoutProxy(proxyURL)
+ if err != nil {
+ return nil, err
+ }
+
+ proxyClientConn := httputil.NewProxyClientConn(proxyDialConn, nil)
+ _, err = proxyClientConn.Do(&proxyReq)
+ if err != nil && err != httputil.ErrPersistEOF {
+ return nil, err
+ }
+
+ rwc, _ := proxyClientConn.Hijack()
+
+ if req.URL.Scheme != "https" {
+ return rwc, nil
+ }
+
+ host, _, err := net.SplitHostPort(targetHost)
+ if err != nil {
+ return nil, err
+ }
+
+ tlsConfig := s.tlsConfig
+ switch {
+ case tlsConfig == nil:
+ tlsConfig = &tls.Config{ServerName: host}
+ case len(tlsConfig.ServerName) == 0:
+ tlsConfig = tlsConfig.Clone()
+ tlsConfig.ServerName = host
+ }
+
+ tlsConn := tls.Client(rwc, tlsConfig)
+
+ // need to manually call Handshake() so we can call VerifyHostname() below
+ if err := tlsConn.Handshake(); err != nil {
+ return nil, err
+ }
+
+ // Return if we were configured to skip validation
+ if tlsConfig.InsecureSkipVerify {
+ return tlsConn, nil
+ }
+
+ if err := tlsConn.VerifyHostname(tlsConfig.ServerName); err != nil {
+ return nil, err
+ }
+
+ return tlsConn, nil
+}
+
+// dialWithoutProxy dials the host specified by url, using TLS if appropriate.
+func (s *SpdyRoundTripper) dialWithoutProxy(url *url.URL) (net.Conn, error) {
+ dialAddr := netutil.CanonicalAddr(url)
+
+ if url.Scheme == "http" {
+ if s.Dialer == nil {
+ return net.Dial("tcp", dialAddr)
+ } else {
+ return s.Dialer.Dial("tcp", dialAddr)
+ }
+ }
+
+ // TODO validate the TLSClientConfig is set up?
+ var conn *tls.Conn
+ var err error
+ if s.Dialer == nil {
+ conn, err = tls.Dial("tcp", dialAddr, s.tlsConfig)
+ } else {
+ conn, err = tls.DialWithDialer(s.Dialer, "tcp", dialAddr, s.tlsConfig)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ // Return if we were configured to skip validation
+ if s.tlsConfig != nil && s.tlsConfig.InsecureSkipVerify {
+ return conn, nil
+ }
+
+ host, _, err := net.SplitHostPort(dialAddr)
+ if err != nil {
+ return nil, err
+ }
+ if s.tlsConfig != nil && len(s.tlsConfig.ServerName) > 0 {
+ host = s.tlsConfig.ServerName
+ }
+ err = conn.VerifyHostname(host)
+ if err != nil {
+ return nil, err
+ }
+
+ return conn, nil
+}
+
+// proxyAuth returns, for a given proxy URL, the value to be used for the Proxy-Authorization header
+func (s *SpdyRoundTripper) proxyAuth(proxyURL *url.URL) string {
+ if proxyURL == nil || proxyURL.User == nil {
+ return ""
+ }
+ credentials := proxyURL.User.String()
+ encodedAuth := base64.StdEncoding.EncodeToString([]byte(credentials))
+ return fmt.Sprintf("Basic %s", encodedAuth)
+}
+
+// RoundTrip executes the Request and upgrades it. After a successful upgrade,
+// clients may call SpdyRoundTripper.Connection() to retrieve the upgraded
+// connection.
+func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+ header := utilnet.CloneHeader(req.Header)
+ header.Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
+ header.Add(httpstream.HeaderUpgrade, HeaderSpdy31)
+
+ var (
+ conn net.Conn
+ rawResponse []byte
+ err error
+ )
+
+ if s.followRedirects {
+ conn, rawResponse, err = utilnet.ConnectWithRedirects(req.Method, req.URL, header, req.Body, s)
+ } else {
+ clone := utilnet.CloneRequest(req)
+ clone.Header = header
+ conn, err = s.Dial(clone)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ responseReader := bufio.NewReader(
+ io.MultiReader(
+ bytes.NewBuffer(rawResponse),
+ conn,
+ ),
+ )
+
+ resp, err := http.ReadResponse(responseReader, nil)
+ if err != nil {
+ if conn != nil {
+ conn.Close()
+ }
+ return nil, err
+ }
+
+ s.conn = conn
+
+ return resp, nil
+}
+
+// NewConnection validates the upgrade response, creating and returning a new
+// httpstream.Connection if there were no errors.
+func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (httpstream.Connection, error) {
+ connectionHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderConnection))
+ upgradeHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderUpgrade))
+ if (resp.StatusCode != http.StatusSwitchingProtocols) || !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
+ defer resp.Body.Close()
+ responseError := ""
+ responseErrorBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ responseError = "unable to read error from server response"
+ } else {
+ // TODO: I don't belong here, I should be abstracted from this class
+ if obj, _, err := statusCodecs.UniversalDecoder().Decode(responseErrorBytes, nil, &metav1.Status{}); err == nil {
+ if status, ok := obj.(*metav1.Status); ok {
+ return nil, &apierrors.StatusError{ErrStatus: *status}
+ }
+ }
+ responseError = string(responseErrorBytes)
+ responseError = strings.TrimSpace(responseError)
+ }
+
+ return nil, fmt.Errorf("unable to upgrade connection: %s", responseError)
+ }
+
+ return NewClientConnection(s.conn)
+}
+
+// statusScheme is private scheme for the decoding here until someone fixes the TODO in NewConnection
+var statusScheme = runtime.NewScheme()
+
+// ParameterCodec knows about query parameters used with the meta v1 API spec.
+var statusCodecs = serializer.NewCodecFactory(statusScheme)
+
+func init() {
+ statusScheme.AddUnversionedTypes(metav1.SchemeGroupVersion,
+ &metav1.Status{},
+ )
+}
diff --git a/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go
new file mode 100644
index 000000000..13353988f
--- /dev/null
+++ b/vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go
@@ -0,0 +1,107 @@
+/*
+Copyright 2015 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package spdy
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "strings"
+ "sync/atomic"
+
+ "k8s.io/apimachinery/pkg/util/httpstream"
+ "k8s.io/apimachinery/pkg/util/runtime"
+)
+
+const HeaderSpdy31 = "SPDY/3.1"
+
+// responseUpgrader knows how to upgrade HTTP responses. It
+// implements the httpstream.ResponseUpgrader interface.
+type responseUpgrader struct {
+}
+
+// connWrapper is used to wrap a hijacked connection and its bufio.Reader. All
+// calls will be handled directly by the underlying net.Conn with the exception
+// of Read and Close calls, which will consider data in the bufio.Reader. This
+// ensures that data already inside the used bufio.Reader instance is also
+// read.
+type connWrapper struct {
+ net.Conn
+ closed int32
+ bufReader *bufio.Reader
+}
+
+func (w *connWrapper) Read(b []byte) (n int, err error) {
+ if atomic.LoadInt32(&w.closed) == 1 {
+ return 0, io.EOF
+ }
+ return w.bufReader.Read(b)
+}
+
+func (w *connWrapper) Close() error {
+ err := w.Conn.Close()
+ atomic.StoreInt32(&w.closed, 1)
+ return err
+}
+
+// NewResponseUpgrader returns a new httpstream.ResponseUpgrader that is
+// capable of upgrading HTTP responses using SPDY/3.1 via the
+// spdystream package.
+func NewResponseUpgrader() httpstream.ResponseUpgrader {
+ return responseUpgrader{}
+}
+
+// UpgradeResponse upgrades an HTTP response to one that supports multiplexed
+// streams. newStreamHandler will be called synchronously whenever the
+// other end of the upgraded connection creates a new stream.
+func (u responseUpgrader) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection {
+ connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection))
+ upgradeHeader := strings.ToLower(req.Header.Get(httpstream.HeaderUpgrade))
+ if !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprintf(w, "unable to upgrade: missing upgrade headers in request: %#v", req.Header)
+ return nil
+ }
+
+ hijacker, ok := w.(http.Hijacker)
+ if !ok {
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprintf(w, "unable to upgrade: unable to hijack response")
+ return nil
+ }
+
+ w.Header().Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
+ w.Header().Add(httpstream.HeaderUpgrade, HeaderSpdy31)
+ w.WriteHeader(http.StatusSwitchingProtocols)
+
+ conn, bufrw, err := hijacker.Hijack()
+ if err != nil {
+ runtime.HandleError(fmt.Errorf("unable to upgrade: error hijacking response: %v", err))
+ return nil
+ }
+
+ connWithBuf := &connWrapper{Conn: conn, bufReader: bufrw.Reader}
+ spdyConn, err := NewServerConnection(connWithBuf, newStreamHandler)
+ if err != nil {
+ runtime.HandleError(fmt.Errorf("unable to upgrade: error creating SPDY server connection: %v", err))
+ return nil
+ }
+
+ return spdyConn
+}