// RoundTrip handles the actual request; ensuring a connection is // made, determining which protocol to use, and performing the // request. func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { u := req.URL // Make sure the URL host contains the port. if !strings.Contains(u.Host, ":") { switch u.Scheme { case "http": u.Host += ":80" case "https": u.Host += ":443" } } conn, tcpConn, err := t.process(req) if err != nil { return nil, err } if tcpConn != nil { return t.doHTTP(tcpConn, req) } // The connection has now been established. debug.Printf("Requesting %q over SPDY.\n", u.String()) // Determine the request priority. var priority common.Priority if t.Priority != nil { priority = t.Priority(req.URL) } else { priority = common.DefaultPriority(req.URL) } res, err := conn.RequestResponse(req, t.Receiver, priority) if conn.Closed() { t.connLimit[u.Host] <- struct{}{} } if err != nil { return nil, err } return res, nil }
// test tests a site to see if it supports various protocols func (s *site) test(l *os.File) { // Check the name resolves, give up if it does not s.resolves.ran = true _, err := net.LookupHost(s.name) if err != nil { s.logf(l, "Error resolving name: %s", err) s.resolves.yesno = false return } s.resolves.yesno = true // See if port 443 is open, give up if it is not hostPort := net.JoinHostPort(s.name, "443") s.port443Open.ran = true c, err := net.DialTimeout("tcp", hostPort, 2*time.Second) if err != nil { s.logf(l, "TCP dial to port 443 failed: %s", err) s.port443Open.yesno = false return } c.Close() s.port443Open.yesno = true // See if TLS works and if SPDY/3.1 or HTTP/2 offered. Give up if // TLS does not work. If the name doesn't work try adding www. to // se if it's a TLS certificate error. s.tlsWorks.ran = true config := &tls.Config{} config.ServerName = s.name timeoutDialer := &net.Dialer{} timeoutDialer.Timeout = 2 * time.Second tc, err := tls.DialWithDialer(timeoutDialer, "tcp", hostPort, config) if err != nil { s.name = "www." + s.name config.ServerName = s.name hostPort = net.JoinHostPort(s.name, "443") tc, err = tls.DialWithDialer(timeoutDialer, "tcp", hostPort, config) if err != nil { s.logf(l, "Error performing TLS connection: %s", err) s.tlsWorks.yesno = false return } } s.tlsWorks.yesno = true // Retrieve the list of NPN offered protocols and check for // spdy/3.1 and h2 cs := tc.ConnectionState() s.npn = cs.OfferedProtocols s.spdyAnnounced.ran = true s.spdyAnnounced.yesno = in(cs.OfferedProtocols, "spdy/3.1") s.http2Announced.ran = true s.http2Announced.yesno = in(cs.OfferedProtocols, "h2") tc.Close() // See if HTTPS works by performing GET / s.httpsWorks.ran = true httpTransport := &http.Transport{} req, _ := http.NewRequest("GET", "https://"+s.name, nil) resp, err := httpTransport.RoundTrip(req) if err != nil { s.logf(l, "HTTP request failed: %s", err) return } if resp != nil && resp.Body != nil { _, _ = ioutil.ReadAll(resp.Body) resp.Body.Close() } httpTransport.CloseIdleConnections() s.httpsWorks.yesno = err == nil // See if SPDY works by doing GET / over SPDY if s.spdyAnnounced.yesno { s.spdyWorks.ran = true conf := &tls.Config{} conf.NextProtos = []string{"spdy/3.1"} spdyC, err := tls.DialWithDialer(timeoutDialer, "tcp", hostPort, conf) if err == nil { spdyC.SetDeadline(time.Now().Add(time.Minute)) defer spdyC.Close() cs = spdyC.ConnectionState() if cs.NegotiatedProtocol != "spdy/3.1" { s.logf(l, "NegotiatedProtocol not spdy/3.1: %s", cs.NegotiatedProtocol) } else { sc, err := spdy.NewClientConn(spdyC, nil, 3, 1) if err == nil { defer sc.Close() go sc.Run() req, _ := http.NewRequest("GET", "https://"+s.name, nil) resp, err := sc.RequestResponse(req, nil, common.DefaultPriority(req.URL)) if err != nil { s.logf(l, "Failed to do SPDY request: %s", err) } s.spdyWorks.yesno = err == nil if resp != nil && resp.Body != nil { _, _ = ioutil.ReadAll(resp.Body) resp.Body.Close() } } else { s.logf(l, "Failed create SPDY client connection: %s", err) } } } else { s.logf(l, "Failed to dial port 443 for SPDY: %s", err) } } // See if HTTP/2 works by doing GET / using HTTP/2 if s.http2Announced.yesno { s.http2Works.ran = true conf := &tls.Config{} conf.NextProtos = []string{"h2"} h2C, err := tls.DialWithDialer(timeoutDialer, "tcp", hostPort, conf) if err == nil { h2C.SetDeadline(time.Now().Add(time.Minute)) defer h2C.Close() cs = h2C.ConnectionState() if cs.NegotiatedProtocol != "h2" { s.logf(l, "NegotiatedProtocol not h2: %s", cs.NegotiatedProtocol) } else { h2t := &http2.Transport{} h2c, err := h2t.NewClientConn(h2C) if err == nil { req, _ := http.NewRequest("GET", "https://"+s.name, nil) resp, err := h2c.RoundTrip(req) if err != nil { s.logf(l, "HTTP/2 RoundTrip failed: %s", err) } s.http2Works.yesno = err == nil if resp != nil && resp.Body != nil { _, _ = ioutil.ReadAll(resp.Body) resp.Body.Close() } } else { s.logf(l, "Failed create HTTP/2 client connection: %s", err) } h2t.CloseIdleConnections() } } else { s.logf(l, "Failed to dial port 443 for HTTP/2: %s", err) } } }