github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/k8s.io/kubernetes/pkg/util/httpstream/httpstream.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors All rights reserved. 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. 37 type NewStreamHandler func(Stream) error 38 39 // NoOpNewStreamHandler is a stream handler that accepts a new stream and 40 // performs no other logic. 41 func NoOpNewStreamHandler(stream Stream) 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 } 82 83 // Stream represents a bidirectional communications channel that is part of an 84 // upgraded connection. 85 type Stream interface { 86 io.ReadWriteCloser 87 // Reset closes both directions of the stream, indicating that neither client 88 // or server can use it any more. 89 Reset() error 90 // Headers returns the headers used to create the stream. 91 Headers() http.Header 92 // Identifier returns the stream's ID. 93 Identifier() uint32 94 } 95 96 // IsUpgradeRequest returns true if the given request is a connection upgrade request 97 func IsUpgradeRequest(req *http.Request) bool { 98 for _, h := range req.Header[http.CanonicalHeaderKey(HeaderConnection)] { 99 if strings.Contains(strings.ToLower(h), strings.ToLower(HeaderUpgrade)) { 100 return true 101 } 102 } 103 return false 104 } 105 106 func negotiateProtocol(clientProtocols, serverProtocols []string) string { 107 for i := range clientProtocols { 108 for j := range serverProtocols { 109 if clientProtocols[i] == serverProtocols[j] { 110 return clientProtocols[i] 111 } 112 } 113 } 114 return "" 115 } 116 117 // Handshake performs a subprotocol negotiation. If the client did not request 118 // a specific subprotocol, defaultProtocol is used. If the client did request a 119 // subprotocol, Handshake will select the first common value found in 120 // serverProtocols. If a match is found, Handshake adds a response header 121 // indicating the chosen subprotocol. If no match is found, HTTP forbidden is 122 // returned, along with a response header containing the list of protocols the 123 // server can accept. 124 func Handshake(req *http.Request, w http.ResponseWriter, serverProtocols []string, defaultProtocol string) (string, error) { 125 clientProtocols := req.Header[http.CanonicalHeaderKey(HeaderProtocolVersion)] 126 if len(clientProtocols) == 0 { 127 // Kube 1.0 client that didn't support subprotocol negotiation 128 // TODO remove this defaulting logic once Kube 1.0 is no longer supported 129 w.Header().Add(HeaderProtocolVersion, defaultProtocol) 130 return defaultProtocol, nil 131 } 132 133 negotiatedProtocol := negotiateProtocol(clientProtocols, serverProtocols) 134 if len(negotiatedProtocol) == 0 { 135 w.WriteHeader(http.StatusForbidden) 136 for i := range serverProtocols { 137 w.Header().Add(HeaderAcceptedProtocolVersions, serverProtocols[i]) 138 } 139 fmt.Fprintf(w, "unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols) 140 return "", fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server supports %v", clientProtocols, serverProtocols) 141 } 142 143 w.Header().Add(HeaderProtocolVersion, negotiatedProtocol) 144 return negotiatedProtocol, nil 145 }