github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/httpstream/spdy/connection.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 spdy 18 19 import ( 20 "net" 21 "net/http" 22 "sync" 23 "time" 24 25 "github.com/moby/spdystream" 26 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/httpstream" 27 "k8s.io/klog/v2" 28 ) 29 30 // connection maintains state about a spdystream.Connection and its associated 31 // streams. 32 type connection struct { 33 conn *spdystream.Connection 34 streams map[uint32]httpstream.Stream 35 streamLock sync.Mutex 36 newStreamHandler httpstream.NewStreamHandler 37 ping func() (time.Duration, error) 38 } 39 40 // NewClientConnection creates a new SPDY client connection. 41 func NewClientConnection(conn net.Conn) (httpstream.Connection, error) { 42 return NewClientConnectionWithPings(conn, 0) 43 } 44 45 // NewClientConnectionWithPings creates a new SPDY client connection. 46 // 47 // If pingPeriod is non-zero, a background goroutine will send periodic Ping 48 // frames to the server. Use this to keep idle connections through certain load 49 // balancers alive longer. 50 func NewClientConnectionWithPings(conn net.Conn, pingPeriod time.Duration) (httpstream.Connection, error) { 51 spdyConn, err := spdystream.NewConnection(conn, false) 52 if err != nil { 53 defer conn.Close() 54 return nil, err 55 } 56 57 return newConnection(spdyConn, httpstream.NoOpNewStreamHandler, pingPeriod, spdyConn.Ping), nil 58 } 59 60 // NewServerConnection creates a new SPDY server connection. newStreamHandler 61 // will be invoked when the server receives a newly created stream from the 62 // client. 63 func NewServerConnection(conn net.Conn, newStreamHandler httpstream.NewStreamHandler) (httpstream.Connection, error) { 64 return NewServerConnectionWithPings(conn, newStreamHandler, 0) 65 } 66 67 // NewServerConnectionWithPings creates a new SPDY server connection. 68 // newStreamHandler will be invoked when the server receives a newly created 69 // stream from the client. 70 // 71 // If pingPeriod is non-zero, a background goroutine will send periodic Ping 72 // frames to the server. Use this to keep idle connections through certain load 73 // balancers alive longer. 74 func NewServerConnectionWithPings(conn net.Conn, newStreamHandler httpstream.NewStreamHandler, pingPeriod time.Duration) (httpstream.Connection, error) { 75 spdyConn, err := spdystream.NewConnection(conn, true) 76 if err != nil { 77 defer conn.Close() 78 return nil, err 79 } 80 81 return newConnection(spdyConn, newStreamHandler, pingPeriod, spdyConn.Ping), nil 82 } 83 84 // newConnection returns a new connection wrapping conn. newStreamHandler 85 // will be invoked when the server receives a newly created stream from the 86 // client. 87 func newConnection(conn *spdystream.Connection, newStreamHandler httpstream.NewStreamHandler, pingPeriod time.Duration, pingFn func() (time.Duration, error)) httpstream.Connection { 88 c := &connection{ 89 conn: conn, 90 newStreamHandler: newStreamHandler, 91 ping: pingFn, 92 streams: make(map[uint32]httpstream.Stream), 93 } 94 go conn.Serve(c.newSpdyStream) 95 if pingPeriod > 0 && pingFn != nil { 96 go c.sendPings(pingPeriod) 97 } 98 return c 99 } 100 101 // createStreamResponseTimeout indicates how long to wait for the other side to 102 // acknowledge the new stream before timing out. 103 const createStreamResponseTimeout = 30 * time.Second 104 105 // Close first sends a reset for all of the connection's streams, and then 106 // closes the underlying spdystream.Connection. 107 func (c *connection) Close() error { 108 c.streamLock.Lock() 109 for _, s := range c.streams { 110 // calling Reset instead of Close ensures that all streams are fully torn down 111 s.Reset() 112 } 113 c.streams = make(map[uint32]httpstream.Stream, 0) 114 c.streamLock.Unlock() 115 116 // now that all streams are fully torn down, it's safe to call close on the underlying connection, 117 // which should be able to terminate immediately at this point, instead of waiting for any 118 // remaining graceful stream termination. 119 return c.conn.Close() 120 } 121 122 // RemoveStreams can be used to removes a set of streams from the Connection. 123 func (c *connection) RemoveStreams(streams ...httpstream.Stream) { 124 c.streamLock.Lock() 125 for _, stream := range streams { 126 // It may be possible that the provided stream is nil if timed out. 127 if stream != nil { 128 delete(c.streams, stream.Identifier()) 129 } 130 } 131 c.streamLock.Unlock() 132 } 133 134 // CreateStream creates a new stream with the specified headers and registers 135 // it with the connection. 136 func (c *connection) CreateStream(headers http.Header) (httpstream.Stream, error) { 137 stream, err := c.conn.CreateStream(headers, nil, false) 138 if err != nil { 139 return nil, err 140 } 141 if err = stream.WaitTimeout(createStreamResponseTimeout); err != nil { 142 return nil, err 143 } 144 145 c.registerStream(stream) 146 return stream, nil 147 } 148 149 // registerStream adds the stream s to the connection's list of streams that 150 // it owns. 151 func (c *connection) registerStream(s httpstream.Stream) { 152 c.streamLock.Lock() 153 c.streams[s.Identifier()] = s 154 c.streamLock.Unlock() 155 } 156 157 // CloseChan returns a channel that, when closed, indicates that the underlying 158 // spdystream.Connection has been closed. 159 func (c *connection) CloseChan() <-chan bool { 160 return c.conn.CloseChan() 161 } 162 163 // newSpdyStream is the internal new stream handler used by spdystream.Connection.Serve. 164 // It calls connection's newStreamHandler, giving it the opportunity to accept or reject 165 // the stream. If newStreamHandler returns an error, the stream is rejected. If not, the 166 // stream is accepted and registered with the connection. 167 func (c *connection) newSpdyStream(stream *spdystream.Stream) { 168 replySent := make(chan struct{}) 169 err := c.newStreamHandler(stream, replySent) 170 rejectStream := (err != nil) 171 if rejectStream { 172 klog.Warningf("Stream rejected: %v", err) 173 stream.Reset() 174 return 175 } 176 177 c.registerStream(stream) 178 stream.SendReply(http.Header{}, rejectStream) 179 close(replySent) 180 } 181 182 // SetIdleTimeout sets the amount of time the connection may remain idle before 183 // it is automatically closed. 184 func (c *connection) SetIdleTimeout(timeout time.Duration) { 185 c.conn.SetIdleTimeout(timeout) 186 } 187 188 func (c *connection) sendPings(period time.Duration) { 189 t := time.NewTicker(period) 190 defer t.Stop() 191 for { 192 select { 193 case <-c.conn.CloseChan(): 194 return 195 case <-t.C: 196 } 197 if _, err := c.ping(); err != nil { 198 klog.V(3).Infof("SPDY Ping failed: %v", err) 199 // Continue, in case this is a transient failure. 200 // c.conn.CloseChan above will tell us when the connection is 201 // actually closed. 202 } 203 } 204 }