github.com/alejandroEsc/spdy@v0.0.0-20200317064415-01a02f0eb389/spdy.go (about) 1 // Copyright 2014 Jamie Hall. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package spdy 6 7 import ( 8 "crypto/tls" 9 "net/http" 10 "net/url" 11 "strings" 12 13 "github.com/SlyMarbo/spdy/common" 14 "github.com/SlyMarbo/spdy/spdy2" 15 "github.com/SlyMarbo/spdy/spdy3" 16 ) 17 18 // SetMaxBenignErrors is used to modify the maximum number 19 // of minor errors each connection will allow without ending 20 // the session. 21 // 22 // By default, the value is set to 0, disabling checks 23 // and allowing minor errors to go unchecked, although they 24 // will still be reported to the debug logger. If it is 25 // important that no errors go unchecked, such as when testing 26 // another implementation, SetMaxBenignErrors with 1 or higher. 27 func SetMaxBenignErrors(n int) { 28 common.MaxBenignErrors = n 29 } 30 31 // AddSPDY adds SPDY support to srv, and must be called before srv begins serving. 32 func AddSPDY(srv *http.Server) { 33 if srv == nil { 34 return 35 } 36 37 npnStrings := npn() 38 if len(npnStrings) <= 1 { 39 return 40 } 41 if srv.TLSConfig == nil { 42 srv.TLSConfig = new(tls.Config) 43 } 44 if srv.TLSConfig.NextProtos == nil { 45 srv.TLSConfig.NextProtos = npnStrings 46 } else { 47 // Collect compatible alternative protocols. 48 others := make([]string, 0, len(srv.TLSConfig.NextProtos)) 49 for _, other := range srv.TLSConfig.NextProtos { 50 if !strings.Contains(other, "spdy/") && !strings.Contains(other, "http/") { 51 others = append(others, other) 52 } 53 } 54 55 // Start with spdy. 56 srv.TLSConfig.NextProtos = make([]string, 0, len(others)+len(npnStrings)) 57 srv.TLSConfig.NextProtos = append(srv.TLSConfig.NextProtos, npnStrings[:len(npnStrings)-1]...) 58 59 // Add the others. 60 srv.TLSConfig.NextProtos = append(srv.TLSConfig.NextProtos, others...) 61 srv.TLSConfig.NextProtos = append(srv.TLSConfig.NextProtos, "http/1.1") 62 } 63 if srv.TLSNextProto == nil { 64 srv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) 65 } 66 for _, str := range npnStrings { 67 switch str { 68 case "spdy/2": 69 srv.TLSNextProto[str] = spdy2.NextProto 70 case "spdy/3": 71 srv.TLSNextProto[str] = spdy3.NextProto 72 case "spdy/3.1": 73 srv.TLSNextProto[str] = spdy3.NextProto1 74 } 75 } 76 } 77 78 // GetPriority is used to identify the request priority of the 79 // given stream. This can be used to manually enforce stream 80 // priority, although this is already performed by the 81 // library. 82 // If the underlying connection is using HTTP, and not SPDY, 83 // GetPriority will return the ErrNotSPDY error. 84 // 85 // A simple example of finding a stream's priority is: 86 // 87 // import ( 88 // "github.com/SlyMarbo/spdy" 89 // "log" 90 // "net/http" 91 // ) 92 // 93 // func httpHandler(w http.ResponseWriter, r *http.Request) { 94 // priority, err := spdy.GetPriority(w) 95 // if err != nil { 96 // // Non-SPDY connection. 97 // } else { 98 // log.Println(priority) 99 // } 100 // } 101 // 102 // func main() { 103 // http.HandleFunc("/", httpHandler) 104 // log.Printf("About to listen on 10443. Go to https://127.0.0.1:10443/") 105 // err := spdy.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil) 106 // if err != nil { 107 // log.Fatal(err) 108 // } 109 // } 110 func GetPriority(w http.ResponseWriter) (int, error) { 111 if stream, ok := w.(PriorityStream); ok { 112 return int(stream.Priority()), nil 113 } 114 return 0, common.ErrNotSPDY 115 } 116 117 // PingClient is used to send PINGs with SPDY servers. 118 // PingClient takes a ResponseWriter and returns a channel on 119 // which a spdy.Ping will be sent when the PING response is 120 // received. If the channel is closed before a spdy.Ping has 121 // been sent, this indicates that the PING was unsuccessful. 122 // 123 // If the underlying connection is using HTTP, and not SPDY, 124 // PingClient will return the ErrNotSPDY error. 125 // 126 // A simple example of sending a ping is: 127 // 128 // import ( 129 // "github.com/SlyMarbo/spdy" 130 // "log" 131 // "net/http" 132 // ) 133 // 134 // func httpHandler(w http.ResponseWriter, req *http.Request) { 135 // ping, err := spdy.PingClient(w) 136 // if err != nil { 137 // // Non-SPDY connection. 138 // } else { 139 // resp, ok <- ping 140 // if ok { 141 // // Ping was successful. 142 // } 143 // } 144 // 145 // } 146 // 147 // func main() { 148 // http.HandleFunc("/", httpHandler) 149 // log.Printf("About to listen on 10443. Go to https://127.0.0.1:10443/") 150 // err := spdy.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil) 151 // if err != nil { 152 // log.Fatal(err) 153 // } 154 // } 155 func PingClient(w http.ResponseWriter) (<-chan bool, error) { 156 if stream, ok := w.(Stream); !ok { 157 return nil, common.ErrNotSPDY 158 } else { 159 return stream.Conn().(Pinger).Ping() 160 } 161 } 162 163 // PingServer is used to send PINGs with http.Clients using. 164 // SPDY. PingServer takes a ResponseWriter and returns a 165 // channel onwhich a spdy.Ping will be sent when the PING 166 // response is received. If the channel is closed before a 167 // spdy.Ping has been sent, this indicates that the PING was 168 // unsuccessful. 169 // 170 // If the underlying connection is using HTTP, and not SPDY, 171 // PingServer will return the ErrNotSPDY error. 172 // 173 // If an underlying connection has not been made to the given 174 // server, PingServer will return the ErrNotConnected error. 175 // 176 // A simple example of sending a ping is: 177 // 178 // import ( 179 // "github.com/SlyMarbo/spdy" 180 // "net/http" 181 // ) 182 // 183 // func main() { 184 // resp, err := http.Get("https://example.com/") 185 // 186 // // ... 187 // 188 // ping, err := spdy.PingServer(http.DefaultClient, "https://example.com") 189 // if err != nil { 190 // // No SPDY connection. 191 // } else { 192 // resp, ok <- ping 193 // if ok { 194 // // Ping was successful. 195 // } 196 // } 197 // } 198 func PingServer(c http.Client, server string) (<-chan bool, error) { 199 if transport, ok := c.Transport.(*Transport); !ok { 200 return nil, common.ErrNotSPDY 201 } else { 202 u, err := url.Parse(server) 203 if err != nil { 204 return nil, err 205 } 206 // Make sure the URL host contains the port. 207 if !strings.Contains(u.Host, ":") { 208 switch u.Scheme { 209 case "http": 210 u.Host += ":80" 211 212 case "https": 213 u.Host += ":443" 214 } 215 } 216 conn, ok := transport.spdyConns[u.Host] 217 if !ok || conn == nil { 218 return nil, common.ErrNotConnected 219 } 220 return conn.(Pinger).Ping() 221 } 222 } 223 224 // Push is used to send server pushes with SPDY servers. 225 // Push takes a ResponseWriter and the url of the resource 226 // being pushed, and returns a ResponseWriter to which the 227 // push should be written. 228 // 229 // If the underlying connection is using HTTP, and not SPDY, 230 // Push will return the ErrNotSPDY error. 231 // 232 // A simple example of pushing a file is: 233 // 234 // import ( 235 // "github.com/SlyMarbo/spdy" 236 // "log" 237 // "net/http" 238 // ) 239 // 240 // func httpHandler(w http.ResponseWriter, r *http.Request) { 241 // path := r.URL.Scheme + "://" + r.URL.Host + "/javascript.js" 242 // push, err := spdy.Push(w, path) 243 // if err != nil { 244 // // Non-SPDY connection. 245 // } else { 246 // http.ServeFile(push, r, "./javascript.js") // Push the given file. 247 // push.Finish() // Finish the stream once used. 248 // } 249 // 250 // } 251 // 252 // func main() { 253 // http.HandleFunc("/", httpHandler) 254 // log.Printf("About to listen on 10443. Go to https://127.0.0.1:10443/") 255 // err := spdy.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil) 256 // if err != nil { 257 // log.Fatal(err) 258 // } 259 // } 260 func Push(w http.ResponseWriter, url string) (common.PushStream, error) { 261 if stream, ok := w.(Stream); !ok { 262 return nil, common.ErrNotSPDY 263 } else { 264 return stream.Conn().(Pusher).Push(url, stream) 265 } 266 } 267 268 // SetFlowControl can be used to set the flow control mechanism on 269 // the underlying SPDY connection. 270 func SetFlowControl(w http.ResponseWriter, f common.FlowControl) error { 271 if stream, ok := w.(Stream); !ok { 272 return common.ErrNotSPDY 273 } else if controller, ok := stream.Conn().(SetFlowController); !ok { 274 return common.ErrNotSPDY 275 } else { 276 controller.SetFlowControl(f) 277 return nil 278 } 279 } 280 281 // SPDYversion returns the SPDY version being used in the underlying 282 // connection used by the given http.ResponseWriter. This is 0 for 283 // connections not using SPDY. 284 func SPDYversion(w http.ResponseWriter) float64 { 285 if stream, ok := w.(Stream); ok { 286 switch stream := stream.Conn().(type) { 287 case *spdy3.Conn: 288 switch stream.Subversion { 289 case 0: 290 return 3 291 case 1: 292 return 3.1 293 default: 294 return 0 295 } 296 297 case *spdy2.Conn: 298 return 2 299 300 default: 301 return 0 302 } 303 } 304 return 0 305 } 306 307 // UsingSPDY indicates whether a given ResponseWriter is using SPDY. 308 func UsingSPDY(w http.ResponseWriter) bool { 309 _, ok := w.(Stream) 310 return ok 311 }