func TestRoundTripAndNewConnection(t *testing.T) { localhostPool := x509.NewCertPool() if !localhostPool.AppendCertsFromPEM(localhostCert) { t.Errorf("error setting up localhostCert pool") } httpsServerInvalidHostname := func(h http.Handler) *httptest.Server { cert, err := tls.X509KeyPair(exampleCert, exampleKey) if err != nil { t.Errorf("https (invalid hostname): proxy_test: %v", err) } ts := httptest.NewUnstartedServer(h) ts.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, } ts.StartTLS() return ts } httpsServerValidHostname := func(h http.Handler) *httptest.Server { cert, err := tls.X509KeyPair(localhostCert, localhostKey) if err != nil { t.Errorf("https (valid hostname): proxy_test: %v", err) } ts := httptest.NewUnstartedServer(h) ts.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, } ts.StartTLS() return ts } testCases := map[string]struct { serverFunc func(http.Handler) *httptest.Server proxyServerFunc func(http.Handler) *httptest.Server clientTLS *tls.Config serverConnectionHeader string serverUpgradeHeader string serverStatusCode int shouldError bool }{ "no headers": { serverFunc: httptest.NewServer, serverConnectionHeader: "", serverUpgradeHeader: "", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, }, "no upgrade header": { serverFunc: httptest.NewServer, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, }, "no connection header": { serverFunc: httptest.NewServer, serverConnectionHeader: "", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, }, "no switching protocol status code": { serverFunc: httptest.NewServer, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusForbidden, shouldError: true, }, "http": { serverFunc: httptest.NewServer, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "https (invalid hostname + InsecureSkipVerify)": { serverFunc: httpsServerInvalidHostname, clientTLS: &tls.Config{InsecureSkipVerify: true}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "https (invalid hostname + hostname verification)": { serverFunc: httpsServerInvalidHostname, clientTLS: &tls.Config{InsecureSkipVerify: false}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, }, "https (valid hostname + RootCAs)": { serverFunc: httpsServerValidHostname, clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied http->http": { serverFunc: httptest.NewServer, proxyServerFunc: httptest.NewServer, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied https (invalid hostname + InsecureSkipVerify) -> http": { serverFunc: httptest.NewServer, proxyServerFunc: httpsServerInvalidHostname, clientTLS: &tls.Config{InsecureSkipVerify: true}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied https (invalid hostname + hostname verification) -> http": { serverFunc: httptest.NewServer, proxyServerFunc: httpsServerInvalidHostname, clientTLS: &tls.Config{InsecureSkipVerify: false}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, // fails because the client doesn't trust the proxy }, "proxied https (valid hostname + RootCAs) -> http": { serverFunc: httptest.NewServer, proxyServerFunc: httpsServerValidHostname, clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, "proxied https (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": { serverFunc: httpsServerInvalidHostname, proxyServerFunc: httpsServerInvalidHostname, clientTLS: &tls.Config{InsecureSkipVerify: true}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, // works because the test proxy ignores TLS errors }, "proxied https (invalid hostname + hostname verification) -> https (invalid hostname)": { serverFunc: httpsServerInvalidHostname, proxyServerFunc: httpsServerInvalidHostname, clientTLS: &tls.Config{InsecureSkipVerify: false}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: true, // fails because the client doesn't trust the proxy }, "proxied https (valid hostname + RootCAs) -> https (valid hostname + RootCAs)": { serverFunc: httpsServerValidHostname, proxyServerFunc: httpsServerValidHostname, clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, } for k, testCase := range testCases { server := testCase.serverFunc(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if testCase.shouldError { if e, a := httpstream.HeaderUpgrade, req.Header.Get(httpstream.HeaderConnection); e != a { t.Fatalf("%s: Expected connection=upgrade header, got '%s", k, a) } w.Header().Set(httpstream.HeaderConnection, testCase.serverConnectionHeader) w.Header().Set(httpstream.HeaderUpgrade, testCase.serverUpgradeHeader) w.WriteHeader(testCase.serverStatusCode) return } streamCh := make(chan httpstream.Stream) responseUpgrader := NewResponseUpgrader() spdyConn := responseUpgrader.UpgradeResponse(w, req, func(s httpstream.Stream, replySent <-chan struct{}) error { streamCh <- s return nil }) if spdyConn == nil { t.Fatalf("%s: unexpected nil spdyConn", k) } defer spdyConn.Close() stream := <-streamCh io.Copy(stream, stream) })) // TODO: Uncomment when fix #19254 // defer server.Close() serverURL, err := url.Parse(server.URL) if err != nil { t.Fatalf("%s: Error creating request: %s", k, err) } req, err := http.NewRequest("GET", server.URL, nil) if err != nil { t.Fatalf("%s: Error creating request: %s", k, err) } spdyTransport := NewSpdyRoundTripper(testCase.clientTLS) var proxierCalled bool var proxyCalledWithHost string if testCase.proxyServerFunc != nil { proxyHandler := goproxy.NewProxyHttpServer() proxyHandler.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { proxyCalledWithHost = host return goproxy.OkConnect, host }) proxy := testCase.proxyServerFunc(proxyHandler) spdyTransport.proxier = func(proxierReq *http.Request) (*url.URL, error) { proxyURL, err := url.Parse(proxy.URL) if err != nil { return nil, err } proxierCalled = true return proxyURL, nil } // TODO: Uncomment when fix #19254 // defer proxy.Close() } client := &http.Client{Transport: spdyTransport} resp, err := client.Do(req) var conn httpstream.Connection if err == nil { conn, err = spdyTransport.NewConnection(resp) } haveErr := err != nil if e, a := testCase.shouldError, haveErr; e != a { t.Fatalf("%s: shouldError=%t, got %t: %v", k, e, a, err) } if testCase.shouldError { continue } defer conn.Close() if resp.StatusCode != http.StatusSwitchingProtocols { t.Fatalf("%s: expected http 101 switching protocols, got %d", k, resp.StatusCode) } stream, err := conn.CreateStream(http.Header{}) if err != nil { t.Fatalf("%s: error creating client stream: %s", k, err) } n, err := stream.Write([]byte("hello")) if err != nil { t.Fatalf("%s: error writing to stream: %s", k, err) } if n != 5 { t.Fatalf("%s: Expected to write 5 bytes, but actually wrote %d", k, n) } b := make([]byte, 5) n, err = stream.Read(b) if err != nil { t.Fatalf("%s: error reading from stream: %s", k, err) } if n != 5 { t.Fatalf("%s: Expected to read 5 bytes, but actually read %d", k, n) } if e, a := "hello", string(b[0:n]); e != a { t.Fatalf("%s: expected '%s', got '%s'", k, e, a) } if testCase.proxyServerFunc != nil { if !proxierCalled { t.Fatalf("%s: Expected to use a proxy but proxier in SpdyRoundTripper wasn't called", k) } if proxyCalledWithHost != serverURL.Host { t.Fatalf("%s: Expected to see a call to the proxy for backend %q, got %q", k, serverURL.Host, proxyCalledWithHost) } } } }