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  }