// 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 Serverpackage httptestimport ()// 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.typeServerstruct { URL string// base URL of form http://ipaddr:port with no trailing slash Listener net.Listener// EnableHTTP2 controls whether HTTP/2 is enabled // on the server. It must be set between calling // NewUnstartedServer and calling Server.StartTLS. EnableHTTP2 bool// 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// certificate is a parsed version of the TLS config certificate, if present. certificate *x509.Certificate// wg counts the number of outstanding HTTP requests on this server. // Close blocks until all requests are finished. wg sync.WaitGroup mu sync.Mutex// guards closed and conns closed bool conns map[net.Conn]http.ConnState// except terminal states// client is configured for use with the server. // Its transport is automatically closed when Close is called. client *http.Client}func newLocalListener() net.Listener {ifserveFlag != "" { , := net.Listen("tcp", serveFlag)if != nil {panic(fmt.Sprintf("httptest: failed to listen on %v: %v", serveFlag, )) }return } , := net.Listen("tcp", "127.0.0.1:0")if != nil {if , = net.Listen("tcp6", "[::1]:0"); != nil {panic(fmt.Sprintf("httptest: failed to listen on a port: %v", )) } }return}// 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.// We only register this flag if it looks like the caller knows about it// and is trying to use it as we don't want to pollute flags and this// isn't really part of our API. Don't depend on this.var serveFlag stringfunc init() {ifstrSliceContainsPrefix(os.Args, "-httptest.serve=") || strSliceContainsPrefix(os.Args, "--httptest.serve=") {flag.StringVar(&serveFlag, "httptest.serve", "", "if non-empty, httptest.NewServer serves on this address and blocks.") }}func strSliceContainsPrefix( []string, string) bool {for , := range {ifstrings.HasPrefix(, ) {returntrue } }returnfalse}// NewServer starts and returns a new [Server].// The caller should call Close when finished, to shut it down.func ( http.Handler) *Server { := NewUnstartedServer() .Start()return}// 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 ( http.Handler) *Server {return &Server{Listener: newLocalListener(),Config: &http.Server{Handler: }, }}// Start starts a server from NewUnstartedServer.func ( *Server) () {if .URL != "" {panic("Server already started") }if .client == nil { .client = &http.Client{Transport: &http.Transport{}} } .URL = "http://" + .Listener.Addr().String() .wrap() .goServe()ifserveFlag != "" {fmt.Fprintln(os.Stderr, "httptest: serving on", .URL)select {} }}// StartTLS starts TLS on a server from NewUnstartedServer.func ( *Server) () {if .URL != "" {panic("Server already started") }if .client == nil { .client = &http.Client{} } , := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)if != nil {panic(fmt.Sprintf("httptest: NewTLSServer: %v", )) } := .TLSif != nil { .TLS = .Clone() } else { .TLS = new(tls.Config) }if .TLS.NextProtos == nil { := []string{"http/1.1"}if .EnableHTTP2 { = []string{"h2"} } .TLS.NextProtos = }iflen(.TLS.Certificates) == 0 { .TLS.Certificates = []tls.Certificate{} } .certificate, = x509.ParseCertificate(.TLS.Certificates[0].Certificate[0])if != nil {panic(fmt.Sprintf("httptest: NewTLSServer: %v", )) } := x509.NewCertPool() .AddCert(.certificate) .client.Transport = &http.Transport{TLSClientConfig: &tls.Config{RootCAs: , },ForceAttemptHTTP2: .EnableHTTP2, } .Listener = tls.NewListener(.Listener, .TLS) .URL = "https://" + .Listener.Addr().String() .wrap() .goServe()}// NewTLSServer starts and returns a new [Server] using TLS.// The caller should call Close when finished, to shut it down.func ( http.Handler) *Server { := NewUnstartedServer() .StartTLS()return}type closeIdleTransport interface { CloseIdleConnections()}// Close shuts down the server and blocks until all outstanding// requests on this server have completed.func ( *Server) () { .mu.Lock()if !.closed { .closed = true .Listener.Close() .Config.SetKeepAlivesEnabled(false)for , := range .conns {// Force-close any idle connections (those between // requests) and new connections (those which connected // but never sent a request). StateNew connections are // super rare and have only been seen (in // previously-flaky tests) in the case of // socket-late-binding races from the http Client // dialing this server and then getting an idle // connection before the dial completed. There is thus // a connected connection in StateNew with no // associated Request. We only close StateIdle and // StateNew because they're not doing anything. It's // possible StateNew is about to do something in a few // milliseconds, but a previous CL to check again in a // few milliseconds wasn't liked (early versions of // https://golang.org/cl/15151) so now we just // forcefully close StateNew. The docs for Server.Close say // we wait for "outstanding requests", so we don't close things // in StateActive.if == http.StateIdle || == http.StateNew { .closeConn() } }// If this server doesn't shut down in 5 seconds, tell the user why. := time.AfterFunc(5*time.Second, .logCloseHangDebugInfo)defer .Stop() } .mu.Unlock()// Not part of httptest.Server's correctness, but assume most // users of httptest.Server will be using the standard // transport, so help them out and close any idle connections for them.if , := http.DefaultTransport.(closeIdleTransport); { .CloseIdleConnections() }// Also close the client idle connections.if .client != nil {if , := .client.Transport.(closeIdleTransport); { .CloseIdleConnections() } } .wg.Wait()}func ( *Server) () { .mu.Lock()defer .mu.Unlock()varstrings.Builder .WriteString("httptest.Server blocked in Close after 5 seconds, waiting for connections:\n")for , := range .conns {fmt.Fprintf(&, " %T %p %v in state %v\n", , , .RemoteAddr(), ) }log.Print(.String())}// CloseClientConnections closes any open HTTP connections to the test Server.func ( *Server) () { .mu.Lock() := len(.conns) := make(chanstruct{}, )for := range .conns {go .closeConnChan(, ) } .mu.Unlock()// Wait for outstanding closes to finish. // // Out of paranoia for making a late change in Go 1.6, we // bound how long this can wait, since golang.org/issue/14291 // isn't fully understood yet. At least this should only be used // in tests. := time.NewTimer(5 * time.Second)defer .Stop()for := 0; < ; ++ {select {case<-:case<-.C:// Too slow. Give up.return } }}// Certificate returns the certificate used by the server, or nil if// the server doesn't use TLS.func ( *Server) () *x509.Certificate {return .certificate}// Client returns an HTTP client configured for making requests to the server.// It is configured to trust the server's TLS test certificate and will// close its idle connections on [Server.Close].// Use Server.URL as the base URL to send requests to the server.func ( *Server) () *http.Client {return .client}func ( *Server) () { .wg.Add(1)gofunc() {defer .wg.Done() .Config.Serve(.Listener) }()}// wrap installs the connection state-tracking hook to know which// connections are idle.func ( *Server) () { := .Config.ConnState .Config.ConnState = func( net.Conn, http.ConnState) { .mu.Lock()defer .mu.Unlock()switch {casehttp.StateNew:if , := .conns[]; {panic("invalid state transition") }if .conns == nil { .conns = make(map[net.Conn]http.ConnState) }// Add c to the set of tracked conns and increment it to the // waitgroup. .wg.Add(1) .conns[] = if .closed {// Probably just a socket-late-binding dial from // the default transport that lost the race (and // thus this connection is now idle and will // never be used). .closeConn() }casehttp.StateActive:if , := .conns[]; {if != http.StateNew && != http.StateIdle {panic("invalid state transition") } .conns[] = }casehttp.StateIdle:if , := .conns[]; {if != http.StateActive {panic("invalid state transition") } .conns[] = }if .closed { .closeConn() }casehttp.StateHijacked, http.StateClosed:// Remove c from the set of tracked conns and decrement it from the // waitgroup, unless it was previously removed.if , := .conns[]; {delete(.conns, )// Keep Close from returning until the user's ConnState hook // (if any) finishes.defer .wg.Done() } }if != nil { (, ) } }}// closeConn closes c.// s.mu must be held.func ( *Server) ( net.Conn) { .closeConnChan(, nil) }// closeConnChan is like closeConn, but takes an optional channel to receive a value// when the goroutine closing c is done.func ( *Server) ( net.Conn, chan<- struct{}) { .Close()if != nil { <- struct{}{} }}
The pages are generated with Goldsv0.6.9-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds.