github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/httpstream/httpstream.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package httpstream
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"strings"
    24  	"time"
    25  )
    26  
    27  const (
    28  	HeaderConnection               = "Connection"
    29  	HeaderUpgrade                  = "Upgrade"
    30  	HeaderProtocolVersion          = "X-Stream-Protocol-Version"
    31  	HeaderAcceptedProtocolVersions = "X-Accepted-Stream-Protocol-Versions"
    32  )
    33  
    34  // NewStreamHandler defines a function that is called when a new Stream is
    35  // received. If no error is returned, the Stream is accepted; otherwise,
    36  // the stream is rejected. After the reply frame has been sent, replySent is closed.
    37  type NewStreamHandler func(stream Stream, replySent <-chan struct{}) error
    38  
    39  // NoOpNewStreamHandler is a stream handler that accepts a new stream and
    40  // performs no other logic.
    41  func NoOpNewStreamHandler(stream Stream, replySent <-chan struct{}) error { return nil }
    42  
    43  // Dialer knows how to open a streaming connection to a server.
    44  type Dialer interface {
    45  
    46  	// Dial opens a streaming connection to a server using one of the protocols
    47  	// specified (in order of most preferred to least preferred).
    48  	Dial(protocols ...string) (Connection, string, error)
    49  }
    50  
    51  // UpgradeRoundTripper is a type of http.RoundTripper that is able to upgrade
    52  // HTTP requests to support multiplexed bidirectional streams. After RoundTrip()
    53  // is invoked, if the upgrade is successful, clients may retrieve the upgraded
    54  // connection by calling UpgradeRoundTripper.Connection().
    55  type UpgradeRoundTripper interface {
    56  	http.RoundTripper
    57  	// NewConnection validates the response and creates a new Connection.
    58  	NewConnection(resp *http.Response) (Connection, error)
    59  }
    60  
    61  // ResponseUpgrader knows how to upgrade HTTP requests and responses to
    62  // add streaming support to them.
    63  type ResponseUpgrader interface {
    64  	// UpgradeResponse upgrades an HTTP response to one that supports multiplexed
    65  	// streams. newStreamHandler will be called asynchronously whenever the
    66  	// other end of the upgraded connection creates a new stream.
    67  	UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler NewStreamHandler) Connection
    68  }
    69  
    70  // Connection represents an upgraded HTTP connection.
    71  type Connection interface {
    72  	// CreateStream creates a new Stream with the supplied headers.
    73  	CreateStream(headers http.Header) (Stream, error)
    74  	// Close resets all streams and closes the connection.
    75  	Close() error
    76  	// CloseChan returns a channel that is closed when the underlying connection is closed.
    77  	CloseChan() <-chan bool
    78  	// SetIdleTimeout sets the amount of time the connection may remain idle before
    79  	// it is automatically closed.
    80  	SetIdleTimeout(timeout time.Duration)
    81  	// RemoveStreams can be used to remove a set of streams from the Connection.
    82  	RemoveStreams(streams ...Stream)
    83  }
    84  
    85  // Stream represents a bidirectional communications channel that is part of an
    86  // upgraded connection.
    87  type Stream interface {
    88  	io.ReadWriteCloser
    89  	// Reset closes both directions of the stream, indicating that neither client
    90  	// or server can use it any more.
    91  	Reset() error
    92  	// Headers returns the headers used to create the stream.
    93  	Headers() http.Header
    94  	// Identifier returns the stream's ID.
    95  	Identifier() uint32
    96  }
    97  
    98  // IsUpgradeRequest returns true if the given request is a connection upgrade request
    99  func IsUpgradeRequest(req *http.Request) bool {
   100  	for _, h := range req.Header[http.CanonicalHeaderKey(HeaderConnection)] {
   101  		if strings.Contains(strings.ToLower(h), strings.ToLower(HeaderUpgrade)) {
   102  			return true
   103  		}
   104  	}
   105  	return false
   106  }
   107  
   108  func negotiateProtocol(clientProtocols, serverProtocols []string) string {
   109  	for i := range clientProtocols {
   110  		for j := range serverProtocols {
   111  			if clientProtocols[i] == serverProtocols[j] {
   112  				return clientProtocols[i]
   113  			}
   114  		}
   115  	}
   116  	return ""
   117  }
   118  
   119  func commaSeparatedHeaderValues(header []string) []string {
   120  	var parsedClientProtocols []string
   121  	for i := range header {
   122  		for _, clientProtocol := range strings.Split(header[i], ",") {
   123  			if proto := strings.Trim(clientProtocol, " "); len(proto) > 0 {
   124  				parsedClientProtocols = append(parsedClientProtocols, proto)
   125  			}
   126  		}
   127  	}
   128  	return parsedClientProtocols
   129  }
   130  
   131  // Handshake performs a subprotocol negotiation. If the client did request a
   132  // subprotocol, Handshake will select the first common value found in
   133  // serverProtocols. If a match is found, Handshake adds a response header
   134  // indicating the chosen subprotocol. If no match is found, HTTP forbidden is
   135  // returned, along with a response header containing the list of protocols the
   136  // server can accept.
   137  func Handshake(req *http.Request, w http.ResponseWriter, serverProtocols []string) (string, error) {
   138  	clientProtocols := commaSeparatedHeaderValues(req.Header[http.CanonicalHeaderKey(HeaderProtocolVersion)])
   139  	if len(clientProtocols) == 0 {
   140  		return "", fmt.Errorf("unable to upgrade: %s is required", HeaderProtocolVersion)
   141  	}
   142  
   143  	if len(serverProtocols) == 0 {
   144  		panic(fmt.Errorf("unable to upgrade: serverProtocols is required"))
   145  	}
   146  
   147  	negotiatedProtocol := negotiateProtocol(clientProtocols, serverProtocols)
   148  	if len(negotiatedProtocol) == 0 {
   149  		for i := range serverProtocols {
   150  			w.Header().Add(HeaderAcceptedProtocolVersions, serverProtocols[i])
   151  		}
   152  		err := fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols)
   153  		http.Error(w, err.Error(), http.StatusForbidden)
   154  		return "", err
   155  	}
   156  
   157  	w.Header().Add(HeaderProtocolVersion, negotiatedProtocol)
   158  	return negotiatedProtocol, nil
   159  }