blob: f5d4e0c1a66bc2618db81bb6feb391889bef0bef [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.17
// +build go1.17
package http2
import (
"context"
"crypto/tls"
"errors"
"net/http"
"net/http/httptest"
"testing"
)
func TestTransportDialTLSContext(t *testing.T) {
blockCh := make(chan struct{})
serverTLSConfigFunc := func(ts *httptest.Server) {
ts.Config.TLSConfig = &tls.Config{
// Triggers the server to request the clients certificate
// during TLS handshake.
ClientAuth: tls.RequestClientCert,
}
}
ts := newServerTester(t,
func(w http.ResponseWriter, r *http.Request) {},
optOnlyServer,
serverTLSConfigFunc,
)
defer ts.Close()
tr := &Transport{
TLSClientConfig: &tls.Config{
GetClientCertificate: func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
// Tests that the context provided to `req` is
// passed into this function.
close(blockCh)
<-cri.Context().Done()
return nil, cri.Context().Err()
},
InsecureSkipVerify: true,
},
}
defer tr.CloseIdleConnections()
req, err := http.NewRequest(http.MethodGet, ts.ts.URL, nil)
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
req = req.WithContext(ctx)
errCh := make(chan error)
go func() {
defer close(errCh)
res, err := tr.RoundTrip(req)
if err != nil {
errCh <- err
return
}
res.Body.Close()
}()
// Wait for GetClientCertificate handler to be called
<-blockCh
// Cancel the context
cancel()
// Expect the cancellation error here
err = <-errCh
if err == nil {
t.Fatal("cancelling context during client certificate fetch did not error as expected")
return
}
if !errors.Is(err, context.Canceled) {
t.Fatalf("unexpected error returned after cancellation: %v", err)
}
}
// TestDialRaceResumesDial tests that, given two concurrent requests
// to the same address, when the first Dial is interrupted because
// the first request's context is cancelled, the second request
// resumes the dial automatically.
func TestDialRaceResumesDial(t *testing.T) {
blockCh := make(chan struct{})
serverTLSConfigFunc := func(ts *httptest.Server) {
ts.Config.TLSConfig = &tls.Config{
// Triggers the server to request the clients certificate
// during TLS handshake.
ClientAuth: tls.RequestClientCert,
}
}
ts := newServerTester(t,
func(w http.ResponseWriter, r *http.Request) {},
optOnlyServer,
serverTLSConfigFunc,
)
defer ts.Close()
tr := &Transport{
TLSClientConfig: &tls.Config{
GetClientCertificate: func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
select {
case <-blockCh:
// If we already errored, return without error.
return &tls.Certificate{}, nil
default:
}
close(blockCh)
<-cri.Context().Done()
return nil, cri.Context().Err()
},
InsecureSkipVerify: true,
},
}
defer tr.CloseIdleConnections()
req, err := http.NewRequest(http.MethodGet, ts.ts.URL, nil)
if err != nil {
t.Fatal(err)
}
// Create two requests with independent cancellation.
ctx1, cancel1 := context.WithCancel(context.Background())
defer cancel1()
req1 := req.WithContext(ctx1)
ctx2, cancel2 := context.WithCancel(context.Background())
defer cancel2()
req2 := req.WithContext(ctx2)
errCh := make(chan error)
go func() {
res, err := tr.RoundTrip(req1)
if err != nil {
errCh <- err
return
}
res.Body.Close()
}()
successCh := make(chan struct{})
go func() {
// Don't start request until first request
// has initiated the handshake.
<-blockCh
res, err := tr.RoundTrip(req2)
if err != nil {
errCh <- err
return
}
res.Body.Close()
// Close successCh to indicate that the second request
// made it to the server successfully.
close(successCh)
}()
// Wait for GetClientCertificate handler to be called
<-blockCh
// Cancel the context first
cancel1()
// Expect the cancellation error here
err = <-errCh
if err == nil {
t.Fatal("cancelling context during client certificate fetch did not error as expected")
return
}
if !errors.Is(err, context.Canceled) {
t.Fatalf("unexpected error returned after cancellation: %v", err)
}
select {
case err := <-errCh:
t.Fatalf("unexpected second error: %v", err)
case <-successCh:
}
}