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 }