| // 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/crypto/tls: |
| // go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h |
| var localhostCert = []byte(`-----BEGIN CERTIFICATE----- |
| MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS |
| MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw |
| MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB |
| iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4 |
| iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul |
| rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO |
| BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw |
| AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA |
| AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9 |
| tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs |
| h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM |
| fblo6RBxUQ== |
| -----END CERTIFICATE-----`) |
| |
| // localhostKey is the private key for localhostCert. |
| var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- |
| MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9 |
| SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZB |
| l2+XsDulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB |
| AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet |
| 3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb |
| uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H |
| qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp |
| jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY |
| fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U |
| fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU |
| y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIX |
| qyUBnu3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo |
| f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA== |
| -----END RSA PRIVATE KEY-----`) |