| // Copyright 2011 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. | |
| // Implementation of Server | |
| package httptest | |
| import ( | |
| "crypto/tls" | |
| "flag" | |
| "fmt" | |
| "net" | |
| "net/http" | |
| "os" | |
| "sync" | |
| ) | |
| // A Server is an HTTP server listening on a system-chosen port on the | |
| // local loopback interface, for use in end-to-end HTTP tests. | |
| type Server struct { | |
| URL string // base URL of form http://ipaddr:port with no trailing slash | |
| Listener net.Listener | |
| // TLS is the optional TLS configuration, populated with a new config | |
| // after TLS is started. If set on an unstarted server before StartTLS | |
| // is called, existing fields are copied into the new config. | |
| TLS *tls.Config | |
| // Config may be changed after calling NewUnstartedServer and | |
| // before Start or StartTLS. | |
| Config *http.Server | |
| // wg counts the number of outstanding HTTP requests on this server. | |
| // Close blocks until all requests are finished. | |
| wg sync.WaitGroup | |
| } | |
| // historyListener keeps track of all connections that it's ever | |
| // accepted. | |
| type historyListener struct { | |
| net.Listener | |
| sync.Mutex // protects history | |
| history []net.Conn | |
| } | |
| func (hs *historyListener) Accept() (c net.Conn, err error) { | |
| c, err = hs.Listener.Accept() | |
| if err == nil { | |
| hs.Lock() | |
| hs.history = append(hs.history, c) | |
| hs.Unlock() | |
| } | |
| return | |
| } | |
| func newLocalListener() net.Listener { | |
| if *serve != "" { | |
| l, err := net.Listen("tcp", *serve) | |
| if err != nil { | |
| panic(fmt.Sprintf("httptest: failed to listen on %v: %v", *serve, err)) | |
| } | |
| return l | |
| } | |
| l, err := net.Listen("tcp", "127.0.0.1:0") | |
| if err != nil { | |
| if l, err = net.Listen("tcp6", "[::1]:0"); err != nil { | |
| panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err)) | |
| } | |
| } | |
| return l | |
| } | |
| // When debugging a particular http server-based test, | |
| // this flag lets you run | |
| // go test -run=BrokenTest -httptest.serve=127.0.0.1:8000 | |
| // to start the broken server so you can interact with it manually. | |
| var serve = flag.String("httptest.serve", "", "if non-empty, httptest.NewServer serves on this address and blocks") | |
| // NewServer starts and returns a new Server. | |
| // The caller should call Close when finished, to shut it down. | |
| func NewServer(handler http.Handler) *Server { | |
| ts := NewUnstartedServer(handler) | |
| ts.Start() | |
| return ts | |
| } | |
| // NewUnstartedServer returns a new Server but doesn't start it. | |
| // | |
| // After changing its configuration, the caller should call Start or | |
| // StartTLS. | |
| // | |
| // The caller should call Close when finished, to shut it down. | |
| func NewUnstartedServer(handler http.Handler) *Server { | |
| return &Server{ | |
| Listener: newLocalListener(), | |
| Config: &http.Server{Handler: handler}, | |
| } | |
| } | |
| // Start starts a server from NewUnstartedServer. | |
| func (s *Server) Start() { | |
| if s.URL != "" { | |
| panic("Server already started") | |
| } | |
| s.Listener = &historyListener{Listener: s.Listener} | |
| s.URL = "http://" + s.Listener.Addr().String() | |
| s.wrapHandler() | |
| go s.Config.Serve(s.Listener) | |
| if *serve != "" { | |
| fmt.Fprintln(os.Stderr, "httptest: serving on", s.URL) | |
| select {} | |
| } | |
| } | |
| // StartTLS starts TLS on a server from NewUnstartedServer. | |
| func (s *Server) StartTLS() { | |
| if s.URL != "" { | |
| panic("Server already started") | |
| } | |
| cert, err := tls.X509KeyPair(localhostCert, localhostKey) | |
| if err != nil { | |
| panic(fmt.Sprintf("httptest: NewTLSServer: %v", err)) | |
| } | |
| existingConfig := s.TLS | |
| s.TLS = new(tls.Config) | |
| if existingConfig != nil { | |
| *s.TLS = *existingConfig | |
| } | |
| if s.TLS.NextProtos == nil { | |
| s.TLS.NextProtos = []string{"http/1.1"} | |
| } | |
| if len(s.TLS.Certificates) == 0 { | |
| s.TLS.Certificates = []tls.Certificate{cert} | |
| } | |
| tlsListener := tls.NewListener(s.Listener, s.TLS) | |
| s.Listener = &historyListener{Listener: tlsListener} | |
| s.URL = "https://" + s.Listener.Addr().String() | |
| s.wrapHandler() | |
| go s.Config.Serve(s.Listener) | |
| } | |
| func (s *Server) wrapHandler() { | |
| h := s.Config.Handler | |
| if h == nil { | |
| h = http.DefaultServeMux | |
| } | |
| s.Config.Handler = &waitGroupHandler{ | |
| s: s, | |
| h: h, | |
| } | |
| } | |
| // NewTLSServer starts and returns a new Server using TLS. | |
| // The caller should call Close when finished, to shut it down. | |
| func NewTLSServer(handler http.Handler) *Server { | |
| ts := NewUnstartedServer(handler) | |
| ts.StartTLS() | |
| return ts | |
| } | |
| // Close shuts down the server and blocks until all outstanding | |
| // requests on this server have completed. | |
| func (s *Server) Close() { | |
| s.Listener.Close() | |
| s.wg.Wait() | |
| s.CloseClientConnections() | |
| if t, ok := http.DefaultTransport.(*http.Transport); ok { | |
| t.CloseIdleConnections() | |
| } | |
| } | |
| // CloseClientConnections closes any currently open HTTP connections | |
| // to the test Server. | |
| func (s *Server) CloseClientConnections() { | |
| hl, ok := s.Listener.(*historyListener) | |
| if !ok { | |
| return | |
| } | |
| hl.Lock() | |
| for _, conn := range hl.history { | |
| conn.Close() | |
| } | |
| hl.Unlock() | |
| } | |
| // waitGroupHandler wraps a handler, incrementing and decrementing a | |
| // sync.WaitGroup on each request, to enable Server.Close to block | |
| // until outstanding requests are finished. | |
| type waitGroupHandler struct { | |
| s *Server | |
| h http.Handler // non-nil | |
| } | |
| func (h *waitGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
| h.s.wg.Add(1) | |
| defer h.s.wg.Done() // a defer, in case ServeHTTP below panics | |
| h.h.ServeHTTP(w, r) | |
| } | |
| // localhostCert is a PEM-encoded TLS cert with SAN IPs | |
| // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end | |
| // of ASN.1 time). | |
| // generated from src/pkg/crypto/tls: | |
| // go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h | |
| var localhostCert = []byte(`-----BEGIN CERTIFICATE----- | |
| MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD | |
| bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj | |
| bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa | |
| IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA | |
| AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud | |
| EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA | |
| AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk | |
| Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA== | |
| -----END CERTIFICATE-----`) | |
| // localhostKey is the private key for localhostCert. | |
| var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- | |
| MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0 | |
| 0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV | |
| NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d | |
| AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW | |
| MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD | |
| EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA | |
| 1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE= | |
| -----END RSA PRIVATE KEY-----`) |