| // 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. | |
| // Bridge package to expose http internals to tests in the http_test | |
| // package. | |
| package http | |
| import ( | |
| "context" | |
| "fmt" | |
| "net" | |
| "net/url" | |
| "sort" | |
| "sync" | |
| "testing" | |
| "time" | |
| ) | |
| var ( | |
| DefaultUserAgent = defaultUserAgent | |
| NewLoggingConn = newLoggingConn | |
| ExportAppendTime = appendTime | |
| ExportRefererForURL = refererForURL | |
| ExportServerNewConn = (*Server).newConn | |
| ExportCloseWriteAndWait = (*conn).closeWriteAndWait | |
| ExportErrRequestCanceled = errRequestCanceled | |
| ExportErrRequestCanceledConn = errRequestCanceledConn | |
| ExportErrServerClosedIdle = errServerClosedIdle | |
| ExportServeFile = serveFile | |
| ExportScanETag = scanETag | |
| ExportHttp2ConfigureServer = http2ConfigureServer | |
| Export_shouldCopyHeaderOnRedirect = shouldCopyHeaderOnRedirect | |
| Export_writeStatusLine = writeStatusLine | |
| Export_is408Message = is408Message | |
| ) | |
| const MaxWriteWaitBeforeConnReuse = maxWriteWaitBeforeConnReuse | |
| func init() { | |
| // We only want to pay for this cost during testing. | |
| // When not under test, these values are always nil | |
| // and never assigned to. | |
| testHookMu = new(sync.Mutex) | |
| testHookClientDoResult = func(res *Response, err error) { | |
| if err != nil { | |
| if _, ok := err.(*url.Error); !ok { | |
| panic(fmt.Sprintf("unexpected Client.Do error of type %T; want *url.Error", err)) | |
| } | |
| } else { | |
| if res == nil { | |
| panic("Client.Do returned nil, nil") | |
| } | |
| if res.Body == nil { | |
| panic("Client.Do returned nil res.Body and no error") | |
| } | |
| } | |
| } | |
| } | |
| func CondSkipHTTP2(t *testing.T) { | |
| if omitBundledHTTP2 { | |
| t.Skip("skipping HTTP/2 test when nethttpomithttp2 build tag in use") | |
| } | |
| } | |
| var ( | |
| SetEnterRoundTripHook = hookSetter(&testHookEnterRoundTrip) | |
| SetRoundTripRetried = hookSetter(&testHookRoundTripRetried) | |
| ) | |
| func SetReadLoopBeforeNextReadHook(f func()) { | |
| testHookMu.Lock() | |
| defer testHookMu.Unlock() | |
| unnilTestHook(&f) | |
| testHookReadLoopBeforeNextRead = f | |
| } | |
| // SetPendingDialHooks sets the hooks that run before and after handling | |
| // pending dials. | |
| func SetPendingDialHooks(before, after func()) { | |
| unnilTestHook(&before) | |
| unnilTestHook(&after) | |
| testHookPrePendingDial, testHookPostPendingDial = before, after | |
| } | |
| func SetTestHookServerServe(fn func(*Server, net.Listener)) { testHookServerServe = fn } | |
| func NewTestTimeoutHandler(handler Handler, ctx context.Context) Handler { | |
| return &timeoutHandler{ | |
| handler: handler, | |
| testContext: ctx, | |
| // (no body) | |
| } | |
| } | |
| func ResetCachedEnvironment() { | |
| resetProxyConfig() | |
| } | |
| func (t *Transport) NumPendingRequestsForTesting() int { | |
| t.reqMu.Lock() | |
| defer t.reqMu.Unlock() | |
| return len(t.reqCanceler) | |
| } | |
| func (t *Transport) IdleConnKeysForTesting() (keys []string) { | |
| keys = make([]string, 0) | |
| t.idleMu.Lock() | |
| defer t.idleMu.Unlock() | |
| for key := range t.idleConn { | |
| keys = append(keys, key.String()) | |
| } | |
| sort.Strings(keys) | |
| return | |
| } | |
| func (t *Transport) IdleConnKeyCountForTesting() int { | |
| t.idleMu.Lock() | |
| defer t.idleMu.Unlock() | |
| return len(t.idleConn) | |
| } | |
| func (t *Transport) IdleConnStrsForTesting() []string { | |
| var ret []string | |
| t.idleMu.Lock() | |
| defer t.idleMu.Unlock() | |
| for _, conns := range t.idleConn { | |
| for _, pc := range conns { | |
| ret = append(ret, pc.conn.LocalAddr().String()+"/"+pc.conn.RemoteAddr().String()) | |
| } | |
| } | |
| sort.Strings(ret) | |
| return ret | |
| } | |
| func (t *Transport) IdleConnStrsForTesting_h2() []string { | |
| var ret []string | |
| noDialPool := t.h2transport.(*http2Transport).ConnPool.(http2noDialClientConnPool) | |
| pool := noDialPool.http2clientConnPool | |
| pool.mu.Lock() | |
| defer pool.mu.Unlock() | |
| for k, cc := range pool.conns { | |
| for range cc { | |
| ret = append(ret, k) | |
| } | |
| } | |
| sort.Strings(ret) | |
| return ret | |
| } | |
| func (t *Transport) IdleConnCountForTesting(scheme, addr string) int { | |
| t.idleMu.Lock() | |
| defer t.idleMu.Unlock() | |
| key := connectMethodKey{"", scheme, addr, false} | |
| cacheKey := key.String() | |
| for k, conns := range t.idleConn { | |
| if k.String() == cacheKey { | |
| return len(conns) | |
| } | |
| } | |
| return 0 | |
| } | |
| func (t *Transport) IdleConnWaitMapSizeForTesting() int { | |
| t.idleMu.Lock() | |
| defer t.idleMu.Unlock() | |
| return len(t.idleConnWait) | |
| } | |
| func (t *Transport) IsIdleForTesting() bool { | |
| t.idleMu.Lock() | |
| defer t.idleMu.Unlock() | |
| return t.closeIdle | |
| } | |
| func (t *Transport) QueueForIdleConnForTesting() { | |
| t.queueForIdleConn(nil) | |
| } | |
| // PutIdleTestConn reports whether it was able to insert a fresh | |
| // persistConn for scheme, addr into the idle connection pool. | |
| func (t *Transport) PutIdleTestConn(scheme, addr string) bool { | |
| c, _ := net.Pipe() | |
| key := connectMethodKey{"", scheme, addr, false} | |
| if t.MaxConnsPerHost > 0 { | |
| // Transport is tracking conns-per-host. | |
| // Increment connection count to account | |
| // for new persistConn created below. | |
| t.connsPerHostMu.Lock() | |
| if t.connsPerHost == nil { | |
| t.connsPerHost = make(map[connectMethodKey]int) | |
| } | |
| t.connsPerHost[key]++ | |
| t.connsPerHostMu.Unlock() | |
| } | |
| return t.tryPutIdleConn(&persistConn{ | |
| t: t, | |
| conn: c, // dummy | |
| closech: make(chan struct{}), // so it can be closed | |
| cacheKey: key, | |
| }) == nil | |
| } | |
| // PutIdleTestConnH2 reports whether it was able to insert a fresh | |
| // HTTP/2 persistConn for scheme, addr into the idle connection pool. | |
| func (t *Transport) PutIdleTestConnH2(scheme, addr string, alt RoundTripper) bool { | |
| key := connectMethodKey{"", scheme, addr, false} | |
| if t.MaxConnsPerHost > 0 { | |
| // Transport is tracking conns-per-host. | |
| // Increment connection count to account | |
| // for new persistConn created below. | |
| t.connsPerHostMu.Lock() | |
| if t.connsPerHost == nil { | |
| t.connsPerHost = make(map[connectMethodKey]int) | |
| } | |
| t.connsPerHost[key]++ | |
| t.connsPerHostMu.Unlock() | |
| } | |
| return t.tryPutIdleConn(&persistConn{ | |
| t: t, | |
| alt: alt, | |
| cacheKey: key, | |
| }) == nil | |
| } | |
| // All test hooks must be non-nil so they can be called directly, | |
| // but the tests use nil to mean hook disabled. | |
| func unnilTestHook(f *func()) { | |
| if *f == nil { | |
| *f = nop | |
| } | |
| } | |
| func hookSetter(dst *func()) func(func()) { | |
| return func(fn func()) { | |
| unnilTestHook(&fn) | |
| *dst = fn | |
| } | |
| } | |
| func ExportHttp2ConfigureTransport(t *Transport) error { | |
| t2, err := http2configureTransports(t) | |
| if err != nil { | |
| return err | |
| } | |
| t.h2transport = t2 | |
| return nil | |
| } | |
| func (s *Server) ExportAllConnsIdle() bool { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| for c := range s.activeConn { | |
| st, unixSec := c.getState() | |
| if unixSec == 0 || st != StateIdle { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| func (s *Server) ExportAllConnsByState() map[ConnState]int { | |
| states := map[ConnState]int{} | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| for c := range s.activeConn { | |
| st, _ := c.getState() | |
| states[st] += 1 | |
| } | |
| return states | |
| } | |
| func (r *Request) WithT(t *testing.T) *Request { | |
| return r.WithContext(context.WithValue(r.Context(), tLogKey{}, t.Logf)) | |
| } | |
| func ExportSetH2GoawayTimeout(d time.Duration) (restore func()) { | |
| old := http2goAwayTimeout | |
| http2goAwayTimeout = d | |
| return func() { http2goAwayTimeout = old } | |
| } | |
| func (r *Request) ExportIsReplayable() bool { return r.isReplayable() } | |
| // ExportCloseTransportConnsAbruptly closes all idle connections from | |
| // tr in an abrupt way, just reaching into the underlying Conns and | |
| // closing them, without telling the Transport or its persistConns | |
| // that it's doing so. This is to simulate the server closing connections | |
| // on the Transport. | |
| func ExportCloseTransportConnsAbruptly(tr *Transport) { | |
| tr.idleMu.Lock() | |
| for _, pcs := range tr.idleConn { | |
| for _, pc := range pcs { | |
| pc.conn.Close() | |
| } | |
| } | |
| tr.idleMu.Unlock() | |
| } | |
| // ResponseWriterConnForTesting returns w's underlying connection, if w | |
| // is a regular *response ResponseWriter. | |
| func ResponseWriterConnForTesting(w ResponseWriter) (c net.Conn, ok bool) { | |
| if r, ok := w.(*response); ok { | |
| return r.conn.rwc, true | |
| } | |
| return nil, false | |
| } |