77package httptest
88
99import (
10+ "bytes"
1011 "crypto/tls"
1112 "flag"
1213 "fmt"
14+ "log"
1315 "net"
1416 "net/http"
1517 "os"
18+ "runtime"
1619 "sync"
20+ "time"
1721)
1822
1923// A Server is an HTTP server listening on a system-chosen port on the
@@ -34,24 +38,10 @@ type Server struct {
3438 // wg counts the number of outstanding HTTP requests on this server.
3539 // Close blocks until all requests are finished.
3640 wg sync.WaitGroup
37- }
38-
39- // historyListener keeps track of all connections that it's ever
40- // accepted.
41- type historyListener struct {
42- net.Listener
43- sync.Mutex // protects history
44- history []net.Conn
45- }
4641
47- func (hs * historyListener ) Accept () (c net.Conn , err error ) {
48- c , err = hs .Listener .Accept ()
49- if err == nil {
50- hs .Lock ()
51- hs .history = append (hs .history , c )
52- hs .Unlock ()
53- }
54- return
42+ mu sync.Mutex // guards closed and conns
43+ closed bool
44+ conns map [net.Conn ]http.ConnState // except terminal states
5545}
5646
5747func newLocalListener () net.Listener {
@@ -103,10 +93,9 @@ func (s *Server) Start() {
10393 if s .URL != "" {
10494 panic ("Server already started" )
10595 }
106- s .Listener = & historyListener {Listener : s .Listener }
10796 s .URL = "http://" + s .Listener .Addr ().String ()
108- s .wrapHandler ()
109- go s . Config . Serve ( s . Listener )
97+ s .wrap ()
98+ s . goServe ( )
11099 if * serve != "" {
111100 fmt .Fprintln (os .Stderr , "httptest: serving on" , s .URL )
112101 select {}
@@ -134,23 +123,10 @@ func (s *Server) StartTLS() {
134123 if len (s .TLS .Certificates ) == 0 {
135124 s .TLS .Certificates = []tls.Certificate {cert }
136125 }
137- tlsListener := tls .NewListener (s .Listener , s .TLS )
138-
139- s .Listener = & historyListener {Listener : tlsListener }
126+ s .Listener = tls .NewListener (s .Listener , s .TLS )
140127 s .URL = "https://" + s .Listener .Addr ().String ()
141- s .wrapHandler ()
142- go s .Config .Serve (s .Listener )
143- }
144-
145- func (s * Server ) wrapHandler () {
146- h := s .Config .Handler
147- if h == nil {
148- h = http .DefaultServeMux
149- }
150- s .Config .Handler = & waitGroupHandler {
151- s : s ,
152- h : h ,
153- }
128+ s .wrap ()
129+ s .goServe ()
154130}
155131
156132// NewTLSServer starts and returns a new Server using TLS.
@@ -161,43 +137,139 @@ func NewTLSServer(handler http.Handler) *Server {
161137 return ts
162138}
163139
140+ type closeIdleTransport interface {
141+ CloseIdleConnections ()
142+ }
143+
164144// Close shuts down the server and blocks until all outstanding
165145// requests on this server have completed.
166146func (s * Server ) Close () {
167- s .Listener .Close ()
168- s .wg .Wait ()
169- s .CloseClientConnections ()
170- if t , ok := http .DefaultTransport .(* http.Transport ); ok {
147+ s .mu .Lock ()
148+ if ! s .closed {
149+ s .closed = true
150+ s .Listener .Close ()
151+ s .Config .SetKeepAlivesEnabled (false )
152+ for c , st := range s .conns {
153+ if st == http .StateIdle {
154+ s .closeConn (c )
155+ }
156+ }
157+ // If this server doesn't shut down in 5 seconds, tell the user why.
158+ t := time .AfterFunc (5 * time .Second , s .logCloseHangDebugInfo )
159+ defer t .Stop ()
160+ }
161+ s .mu .Unlock ()
162+
163+ // Not part of httptest.Server's correctness, but assume most
164+ // users of httptest.Server will be using the standard
165+ // transport, so help them out and close any idle connections for them.
166+ if t , ok := http .DefaultTransport .(closeIdleTransport ); ok {
171167 t .CloseIdleConnections ()
172168 }
169+
170+ s .wg .Wait ()
173171}
174172
175- // CloseClientConnections closes any currently open HTTP connections
176- // to the test Server.
173+ func (s * Server ) logCloseHangDebugInfo () {
174+ s .mu .Lock ()
175+ defer s .mu .Unlock ()
176+ var buf bytes.Buffer
177+ buf .WriteString ("httptest.Server blocked in Close after 5 seconds, waiting for connections:\n " )
178+ for c , st := range s .conns {
179+ fmt .Fprintf (& buf , " %T %p %v in state %v\n " , c , c , c .RemoteAddr (), st )
180+ }
181+ log .Print (buf .String ())
182+ }
183+
184+ // CloseClientConnections closes any open HTTP connections to the test Server.
177185func (s * Server ) CloseClientConnections () {
178- hl , ok := s .Listener .(* historyListener )
179- if ! ok {
180- return
186+ s .mu .Lock ()
187+ defer s .mu .Unlock ()
188+ for c := range s .conns {
189+ s .closeConn (c )
181190 }
182- hl .Lock ()
183- for _ , conn := range hl .history {
184- conn .Close ()
191+ }
192+
193+ func (s * Server ) goServe () {
194+ s .wg .Add (1 )
195+ go func () {
196+ defer s .wg .Done ()
197+ s .Config .Serve (s .Listener )
198+ }()
199+ }
200+
201+ // wrap installs the connection state-tracking hook to know which
202+ // connections are idle.
203+ func (s * Server ) wrap () {
204+ oldHook := s .Config .ConnState
205+ s .Config .ConnState = func (c net.Conn , cs http.ConnState ) {
206+ s .mu .Lock ()
207+ defer s .mu .Unlock ()
208+ switch cs {
209+ case http .StateNew :
210+ s .wg .Add (1 )
211+ if _ , exists := s .conns [c ]; exists {
212+ panic ("invalid state transition" )
213+ }
214+ if s .conns == nil {
215+ s .conns = make (map [net.Conn ]http.ConnState )
216+ }
217+ s .conns [c ] = cs
218+ if s .closed {
219+ // Probably just a socket-late-binding dial from
220+ // the default transport that lost the race (and
221+ // thus this connection is now idle and will
222+ // never be used).
223+ s .closeConn (c )
224+ }
225+ case http .StateActive :
226+ if oldState , ok := s .conns [c ]; ok {
227+ if oldState != http .StateNew && oldState != http .StateIdle {
228+ panic ("invalid state transition" )
229+ }
230+ s .conns [c ] = cs
231+ }
232+ case http .StateIdle :
233+ if oldState , ok := s .conns [c ]; ok {
234+ if oldState != http .StateActive {
235+ panic ("invalid state transition" )
236+ }
237+ s .conns [c ] = cs
238+ }
239+ if s .closed {
240+ s .closeConn (c )
241+ }
242+ case http .StateHijacked , http .StateClosed :
243+ s .forgetConn (c )
244+ }
245+ if oldHook != nil {
246+ oldHook (c , cs )
247+ }
185248 }
186- hl .Unlock ()
187249}
188250
189- // waitGroupHandler wraps a handler, incrementing and decrementing a
190- // sync.WaitGroup on each request, to enable Server.Close to block
191- // until outstanding requests are finished.
192- type waitGroupHandler struct {
193- s * Server
194- h http.Handler // non-nil
251+ // closeConn closes c. Except on plan9, which is special. See comment below.
252+ // s.mu must be held.
253+ func (s * Server ) closeConn (c net.Conn ) {
254+ if runtime .GOOS == "plan9" {
255+ // Go's Plan 9 net package isn't great at unblocking reads when
256+ // their underlying TCP connections are closed. Don't trust
257+ // that that the ConnState state machine will get to
258+ // StateClosed. Instead, just go there directly. Plan 9 may leak
259+ // resources if the syscall doesn't end up returning. Oh well.
260+ s .forgetConn (c )
261+ }
262+ go c .Close ()
195263}
196264
197- func (h * waitGroupHandler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
198- h .s .wg .Add (1 )
199- defer h .s .wg .Done () // a defer, in case ServeHTTP below panics
200- h .h .ServeHTTP (w , r )
265+ // forgetConn removes c from the set of tracked conns and decrements it from the
266+ // waitgroup, unless it was previously removed.
267+ // s.mu must be held.
268+ func (s * Server ) forgetConn (c net.Conn ) {
269+ if _ , ok := s .conns [c ]; ok {
270+ delete (s .conns , c )
271+ s .wg .Done ()
272+ }
201273}
202274
203275// localhostCert is a PEM-encoded TLS cert with SAN IPs
0 commit comments