mirror of
https://github.com/tinygo-org/net.git
synced 2026-07-26 08:18:39 +00:00
Add httptest package
This commit is contained in:
@@ -11,7 +11,7 @@ device. See drivers/netdev for more information on netdev.
|
||||
- [Using "net" and "net/http" Packages](#using-net-and-nethttp-packages)
|
||||
- ["net" Package](#net-package)
|
||||
- [Maintaining "net"](#maintaining-net)
|
||||
|
||||
|
||||
## Using "net" and "net/http" Packages
|
||||
|
||||
See README-net.md in drivers repo to more details on using "net" and "net/http"
|
||||
@@ -28,6 +28,10 @@ there is no mark, it is a straight copy.
|
||||
src/net
|
||||
├── dial.go *
|
||||
├── http
|
||||
│ ├── httptest
|
||||
│ │ ├── httptest.go *
|
||||
│ │ ├── recorder.go
|
||||
│ │ └── server.go *
|
||||
│ ├── client.go *
|
||||
│ ├── clone.go
|
||||
│ ├── cookie.go
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2016 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.
|
||||
|
||||
// Package httptest provides utilities for HTTP testing.
|
||||
package httptest
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewRequest returns a new incoming server Request, suitable
|
||||
// for passing to an http.Handler for testing.
|
||||
//
|
||||
// The target is the RFC 7230 "request-target": it may be either a
|
||||
// path or an absolute URL. If target is an absolute URL, the host name
|
||||
// from the URL is used. Otherwise, "example.com" is used.
|
||||
//
|
||||
// The TLS field is set to a non-nil dummy value if target has scheme
|
||||
// "https".
|
||||
//
|
||||
// The Request.Proto is always HTTP/1.1.
|
||||
//
|
||||
// An empty method means "GET".
|
||||
//
|
||||
// The provided body may be nil. If the body is of type *bytes.Reader,
|
||||
// *strings.Reader, or *bytes.Buffer, the Request.ContentLength is
|
||||
// set.
|
||||
//
|
||||
// NewRequest panics on error for ease of use in testing, where a
|
||||
// panic is acceptable.
|
||||
//
|
||||
// To generate a client HTTP request instead of a server request, see
|
||||
// the NewRequest function in the net/http package.
|
||||
func NewRequest(method, target string, body io.Reader) *http.Request {
|
||||
if method == "" {
|
||||
method = "GET"
|
||||
}
|
||||
req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(method + " " + target + " HTTP/1.0\r\n\r\n")))
|
||||
if err != nil {
|
||||
panic("invalid NewRequest arguments; " + err.Error())
|
||||
}
|
||||
|
||||
// HTTP/1.0 was used above to avoid needing a Host field. Change it to 1.1 here.
|
||||
req.Proto = "HTTP/1.1"
|
||||
req.ProtoMinor = 1
|
||||
req.Close = false
|
||||
|
||||
if body != nil {
|
||||
switch v := body.(type) {
|
||||
case *bytes.Buffer:
|
||||
req.ContentLength = int64(v.Len())
|
||||
case *bytes.Reader:
|
||||
req.ContentLength = int64(v.Len())
|
||||
case *strings.Reader:
|
||||
req.ContentLength = int64(v.Len())
|
||||
default:
|
||||
req.ContentLength = -1
|
||||
}
|
||||
if rc, ok := body.(io.ReadCloser); ok {
|
||||
req.Body = rc
|
||||
} else {
|
||||
req.Body = io.NopCloser(body)
|
||||
}
|
||||
}
|
||||
|
||||
// 192.0.2.0/24 is "TEST-NET" in RFC 5737 for use solely in
|
||||
// documentation and example source code and should not be
|
||||
// used publicly.
|
||||
req.RemoteAddr = "192.0.2.1:1234"
|
||||
|
||||
if req.Host == "" {
|
||||
req.Host = "example.com"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(target, "https://") {
|
||||
// TINYGO: Removed https support
|
||||
panic("not implemented yet")
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
// 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.
|
||||
|
||||
package httptest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ResponseRecorder is an implementation of http.ResponseWriter that
|
||||
// records its mutations for later inspection in tests.
|
||||
type ResponseRecorder struct {
|
||||
// Code is the HTTP response code set by WriteHeader.
|
||||
//
|
||||
// Note that if a Handler never calls WriteHeader or Write,
|
||||
// this might end up being 0, rather than the implicit
|
||||
// http.StatusOK. To get the implicit value, use the Result
|
||||
// method.
|
||||
Code int
|
||||
|
||||
// HeaderMap contains the headers explicitly set by the Handler.
|
||||
// It is an internal detail.
|
||||
//
|
||||
// Deprecated: HeaderMap exists for historical compatibility
|
||||
// and should not be used. To access the headers returned by a handler,
|
||||
// use the Response.Header map as returned by the Result method.
|
||||
HeaderMap http.Header
|
||||
|
||||
// Body is the buffer to which the Handler's Write calls are sent.
|
||||
// If nil, the Writes are silently discarded.
|
||||
Body *bytes.Buffer
|
||||
|
||||
// Flushed is whether the Handler called Flush.
|
||||
Flushed bool
|
||||
|
||||
result *http.Response // cache of Result's return value
|
||||
snapHeader http.Header // snapshot of HeaderMap at first Write
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
func NewRecorder() *ResponseRecorder {
|
||||
return &ResponseRecorder{
|
||||
HeaderMap: make(http.Header),
|
||||
Body: new(bytes.Buffer),
|
||||
Code: 200,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
|
||||
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
|
||||
const DefaultRemoteAddr = "1.2.3.4"
|
||||
|
||||
// Header implements http.ResponseWriter. It returns the response
|
||||
// headers to mutate within a handler. To test the headers that were
|
||||
// written after a handler completes, use the Result method and see
|
||||
// the returned Response value's Header.
|
||||
func (rw *ResponseRecorder) Header() http.Header {
|
||||
m := rw.HeaderMap
|
||||
if m == nil {
|
||||
m = make(http.Header)
|
||||
rw.HeaderMap = m
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// writeHeader writes a header if it was not written yet and
|
||||
// detects Content-Type if needed.
|
||||
//
|
||||
// bytes or str are the beginning of the response body.
|
||||
// We pass both to avoid unnecessarily generate garbage
|
||||
// in rw.WriteString which was created for performance reasons.
|
||||
// Non-nil bytes win.
|
||||
func (rw *ResponseRecorder) writeHeader(b []byte, str string) {
|
||||
if rw.wroteHeader {
|
||||
return
|
||||
}
|
||||
if len(str) > 512 {
|
||||
str = str[:512]
|
||||
}
|
||||
|
||||
m := rw.Header()
|
||||
|
||||
_, hasType := m["Content-Type"]
|
||||
hasTE := m.Get("Transfer-Encoding") != ""
|
||||
if !hasType && !hasTE {
|
||||
if b == nil {
|
||||
b = []byte(str)
|
||||
}
|
||||
m.Set("Content-Type", http.DetectContentType(b))
|
||||
}
|
||||
|
||||
rw.WriteHeader(200)
|
||||
}
|
||||
|
||||
// Write implements http.ResponseWriter. The data in buf is written to
|
||||
// rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
||||
rw.writeHeader(buf, "")
|
||||
if rw.Body != nil {
|
||||
rw.Body.Write(buf)
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// WriteString implements io.StringWriter. The data in str is written
|
||||
// to rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) WriteString(str string) (int, error) {
|
||||
rw.writeHeader(nil, str)
|
||||
if rw.Body != nil {
|
||||
rw.Body.WriteString(str)
|
||||
}
|
||||
return len(str), nil
|
||||
}
|
||||
|
||||
func checkWriteHeaderCode(code int) {
|
||||
// Issue 22880: require valid WriteHeader status codes.
|
||||
// For now we only enforce that it's three digits.
|
||||
// In the future we might block things over 599 (600 and above aren't defined
|
||||
// at https://httpwg.org/specs/rfc7231.html#status.codes)
|
||||
// and we might block under 200 (once we have more mature 1xx support).
|
||||
// But for now any three digits.
|
||||
//
|
||||
// We used to send "HTTP/1.1 000 0" on the wire in responses but there's
|
||||
// no equivalent bogus thing we can realistically send in HTTP/2,
|
||||
// so we'll consistently panic instead and help people find their bugs
|
||||
// early. (We can't return an error from WriteHeader even if we wanted to.)
|
||||
if code < 100 || code > 999 {
|
||||
panic(fmt.Sprintf("invalid WriteHeader code %v", code))
|
||||
}
|
||||
}
|
||||
|
||||
// WriteHeader implements http.ResponseWriter.
|
||||
func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||
if rw.wroteHeader {
|
||||
return
|
||||
}
|
||||
|
||||
checkWriteHeaderCode(code)
|
||||
rw.Code = code
|
||||
rw.wroteHeader = true
|
||||
if rw.HeaderMap == nil {
|
||||
rw.HeaderMap = make(http.Header)
|
||||
}
|
||||
rw.snapHeader = rw.HeaderMap.Clone()
|
||||
}
|
||||
|
||||
// Flush implements http.Flusher. To test whether Flush was
|
||||
// called, see rw.Flushed.
|
||||
func (rw *ResponseRecorder) Flush() {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(200)
|
||||
}
|
||||
rw.Flushed = true
|
||||
}
|
||||
|
||||
// Result returns the response generated by the handler.
|
||||
//
|
||||
// The returned Response will have at least its StatusCode,
|
||||
// Header, Body, and optionally Trailer populated.
|
||||
// More fields may be populated in the future, so callers should
|
||||
// not DeepEqual the result in tests.
|
||||
//
|
||||
// The Response.Header is a snapshot of the headers at the time of the
|
||||
// first write call, or at the time of this call, if the handler never
|
||||
// did a write.
|
||||
//
|
||||
// The Response.Body is guaranteed to be non-nil and Body.Read call is
|
||||
// guaranteed to not return any error other than io.EOF.
|
||||
//
|
||||
// Result must only be called after the handler has finished running.
|
||||
func (rw *ResponseRecorder) Result() *http.Response {
|
||||
if rw.result != nil {
|
||||
return rw.result
|
||||
}
|
||||
if rw.snapHeader == nil {
|
||||
rw.snapHeader = rw.HeaderMap.Clone()
|
||||
}
|
||||
res := &http.Response{
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
StatusCode: rw.Code,
|
||||
Header: rw.snapHeader,
|
||||
}
|
||||
rw.result = res
|
||||
if res.StatusCode == 0 {
|
||||
res.StatusCode = 200
|
||||
}
|
||||
res.Status = fmt.Sprintf("%03d %s", res.StatusCode, http.StatusText(res.StatusCode))
|
||||
if rw.Body != nil {
|
||||
res.Body = io.NopCloser(bytes.NewReader(rw.Body.Bytes()))
|
||||
} else {
|
||||
res.Body = http.NoBody
|
||||
}
|
||||
res.ContentLength = parseContentLength(res.Header.Get("Content-Length"))
|
||||
|
||||
if trailers, ok := rw.snapHeader["Trailer"]; ok {
|
||||
res.Trailer = make(http.Header, len(trailers))
|
||||
for _, k := range trailers {
|
||||
for _, k := range strings.Split(k, ",") {
|
||||
k = http.CanonicalHeaderKey(textproto.TrimString(k))
|
||||
if !validTrailerHeader(k) {
|
||||
// Ignore since forbidden by RFC 7230, section 4.1.2.
|
||||
continue
|
||||
}
|
||||
vv, ok := rw.HeaderMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
vv2 := make([]string, len(vv))
|
||||
copy(vv2, vv)
|
||||
res.Trailer[k] = vv2
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, vv := range rw.HeaderMap {
|
||||
if !strings.HasPrefix(k, http.TrailerPrefix) {
|
||||
continue
|
||||
}
|
||||
if res.Trailer == nil {
|
||||
res.Trailer = make(http.Header)
|
||||
}
|
||||
for _, v := range vv {
|
||||
res.Trailer.Add(strings.TrimPrefix(k, http.TrailerPrefix), v)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// parseContentLength trims whitespace from s and returns -1 if no value
|
||||
// is set, or the value if it's >= 0.
|
||||
//
|
||||
// This a modified version of same function found in net/http/transfer.go. This
|
||||
// one just ignores an invalid header.
|
||||
func parseContentLength(cl string) int64 {
|
||||
cl = textproto.TrimString(cl)
|
||||
if cl == "" {
|
||||
return -1
|
||||
}
|
||||
n, err := strconv.ParseUint(cl, 10, 63)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return int64(n)
|
||||
}
|
||||
|
||||
// ValidTrailerHeader reports whether name is a valid header field name to appear
|
||||
// in trailers.
|
||||
// See RFC 7230, Section 4.1.2
|
||||
// Copied from golang.org/x/net/http/httpguts
|
||||
func validTrailerHeader(name string) bool {
|
||||
name = textproto.CanonicalMIMEHeaderKey(name)
|
||||
if strings.HasPrefix(name, "If-") || badTrailer[name] {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var badTrailer = map[string]bool{
|
||||
"Authorization": true,
|
||||
"Cache-Control": true,
|
||||
"Connection": true,
|
||||
"Content-Encoding": true,
|
||||
"Content-Length": true,
|
||||
"Content-Range": true,
|
||||
"Content-Type": true,
|
||||
"Expect": true,
|
||||
"Host": true,
|
||||
"Keep-Alive": true,
|
||||
"Max-Forwards": true,
|
||||
"Pragma": true,
|
||||
"Proxy-Authenticate": true,
|
||||
"Proxy-Authorization": true,
|
||||
"Proxy-Connection": true,
|
||||
"Range": true,
|
||||
"Realm": true,
|
||||
"Te": true,
|
||||
"Trailer": true,
|
||||
"Transfer-Encoding": true,
|
||||
"Www-Authenticate": true,
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
// TINYGO: Removed https support
|
||||
// TINYGO: Removed closeIdleTransport interface
|
||||
|
||||
// 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 (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// EnableHTTP2 controls whether HTTP/2 is enabled
|
||||
// on the server. It must be set between calling
|
||||
// NewUnstartedServer and calling Server.StartTLS.
|
||||
EnableHTTP2 bool
|
||||
|
||||
// Config may be changed after calling NewUnstartedServer and
|
||||
// before Start or StartTLS.
|
||||
Config *http.Server
|
||||
|
||||
// TINYGO: Removed TLS and certificate properties.
|
||||
|
||||
// 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 {
|
||||
if serveFlag != "" {
|
||||
l, err := net.Listen("tcp", serveFlag)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("httptest: failed to listen on %v: %v", serveFlag, 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.
|
||||
// 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 string
|
||||
|
||||
func init() {
|
||||
if strSliceContainsPrefix(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(v []string, pre string) bool {
|
||||
for _, s := range v {
|
||||
if strings.HasPrefix(s, pre) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
if s.client == nil {
|
||||
// TINYGO: Removed transport
|
||||
s.client = &http.Client{}
|
||||
}
|
||||
s.URL = "http://" + s.Listener.Addr().String()
|
||||
s.wrap()
|
||||
s.goServe()
|
||||
if serveFlag != "" {
|
||||
fmt.Fprintln(os.Stderr, "httptest: serving on", s.URL)
|
||||
select {}
|
||||
}
|
||||
}
|
||||
|
||||
// Close shuts down the server and blocks until all outstanding
|
||||
// requests on this server have completed.
|
||||
func (s *Server) Close() {
|
||||
s.mu.Lock()
|
||||
if !s.closed {
|
||||
s.closed = true
|
||||
s.Listener.Close()
|
||||
s.Config.SetKeepAlivesEnabled(false)
|
||||
for c, st := range s.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 st == http.StateIdle || st == http.StateNew {
|
||||
s.closeConn(c)
|
||||
}
|
||||
}
|
||||
// If this server doesn't shut down in 5 seconds, tell the user why.
|
||||
t := time.AfterFunc(5*time.Second, s.logCloseHangDebugInfo)
|
||||
defer t.Stop()
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
// TINYGO: Removed idle connection closing
|
||||
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
||||
func (s *Server) logCloseHangDebugInfo() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var buf strings.Builder
|
||||
buf.WriteString("httptest.Server blocked in Close after 5 seconds, waiting for connections:\n")
|
||||
for c, st := range s.conns {
|
||||
fmt.Fprintf(&buf, " %T %p %v in state %v\n", c, c, c.RemoteAddr(), st)
|
||||
}
|
||||
log.Print(buf.String())
|
||||
}
|
||||
|
||||
// CloseClientConnections closes any open HTTP connections to the test Server.
|
||||
func (s *Server) CloseClientConnections() {
|
||||
s.mu.Lock()
|
||||
nconn := len(s.conns)
|
||||
ch := make(chan struct{}, nconn)
|
||||
for c := range s.conns {
|
||||
go s.closeConnChan(c, ch)
|
||||
}
|
||||
s.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.
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
defer timer.Stop()
|
||||
for i := 0; i < nconn; i++ {
|
||||
select {
|
||||
case <-ch:
|
||||
case <-timer.C:
|
||||
// Too slow. Give up.
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *Server) Client() *http.Client {
|
||||
return s.client
|
||||
}
|
||||
|
||||
func (s *Server) goServe() {
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
s.Config.Serve(s.Listener)
|
||||
}()
|
||||
}
|
||||
|
||||
// wrap installs the connection state-tracking hook to know which
|
||||
// connections are idle.
|
||||
func (s *Server) wrap() {
|
||||
oldHook := s.Config.ConnState
|
||||
s.Config.ConnState = func(c net.Conn, cs http.ConnState) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
switch cs {
|
||||
case http.StateNew:
|
||||
if _, exists := s.conns[c]; exists {
|
||||
panic("invalid state transition")
|
||||
}
|
||||
if s.conns == nil {
|
||||
s.conns = make(map[net.Conn]http.ConnState)
|
||||
}
|
||||
// Add c to the set of tracked conns and increment it to the
|
||||
// waitgroup.
|
||||
s.wg.Add(1)
|
||||
s.conns[c] = cs
|
||||
if s.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).
|
||||
s.closeConn(c)
|
||||
}
|
||||
case http.StateActive:
|
||||
if oldState, ok := s.conns[c]; ok {
|
||||
if oldState != http.StateNew && oldState != http.StateIdle {
|
||||
panic("invalid state transition")
|
||||
}
|
||||
s.conns[c] = cs
|
||||
}
|
||||
case http.StateIdle:
|
||||
if oldState, ok := s.conns[c]; ok {
|
||||
if oldState != http.StateActive {
|
||||
panic("invalid state transition")
|
||||
}
|
||||
s.conns[c] = cs
|
||||
}
|
||||
if s.closed {
|
||||
s.closeConn(c)
|
||||
}
|
||||
case http.StateHijacked, http.StateClosed:
|
||||
// Remove c from the set of tracked conns and decrement it from the
|
||||
// waitgroup, unless it was previously removed.
|
||||
if _, ok := s.conns[c]; ok {
|
||||
delete(s.conns, c)
|
||||
// Keep Close from returning until the user's ConnState hook
|
||||
// (if any) finishes.
|
||||
defer s.wg.Done()
|
||||
}
|
||||
}
|
||||
if oldHook != nil {
|
||||
oldHook(c, cs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// closeConn closes c.
|
||||
// s.mu must be held.
|
||||
func (s *Server) closeConn(c net.Conn) { s.closeConnChan(c, nil) }
|
||||
|
||||
// closeConnChan is like closeConn, but takes an optional channel to receive a value
|
||||
// when the goroutine closing c is done.
|
||||
func (s *Server) closeConnChan(c net.Conn, done chan<- struct{}) {
|
||||
c.Close()
|
||||
if done != nil {
|
||||
done <- struct{}{}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user