summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/braintree
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/braintree')
-rw-r--r--vendor/github.com/braintree/manners/LICENSE19
-rw-r--r--vendor/github.com/braintree/manners/README.md36
-rw-r--r--vendor/github.com/braintree/manners/helpers_test.go119
-rw-r--r--vendor/github.com/braintree/manners/interfaces.go7
-rw-r--r--vendor/github.com/braintree/manners/server.go292
-rw-r--r--vendor/github.com/braintree/manners/server_test.go289
-rw-r--r--vendor/github.com/braintree/manners/static.go47
-rw-r--r--vendor/github.com/braintree/manners/test_helpers/certs.go29
-rw-r--r--vendor/github.com/braintree/manners/test_helpers/conn.go13
-rw-r--r--vendor/github.com/braintree/manners/test_helpers/listener.go34
-rw-r--r--vendor/github.com/braintree/manners/test_helpers/temp_file.go27
-rw-r--r--vendor/github.com/braintree/manners/test_helpers/wait_group.go33
-rw-r--r--vendor/github.com/braintree/manners/transition_test.go54
13 files changed, 999 insertions, 0 deletions
diff --git a/vendor/github.com/braintree/manners/LICENSE b/vendor/github.com/braintree/manners/LICENSE
new file mode 100644
index 000000000..91ef5beed
--- /dev/null
+++ b/vendor/github.com/braintree/manners/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Braintree, a division of PayPal, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/github.com/braintree/manners/README.md b/vendor/github.com/braintree/manners/README.md
new file mode 100644
index 000000000..09f6f9693
--- /dev/null
+++ b/vendor/github.com/braintree/manners/README.md
@@ -0,0 +1,36 @@
+# Manners
+
+A *polite* webserver for Go.
+
+Manners allows you to shut your Go webserver down gracefully, without dropping any requests. It can act as a drop-in replacement for the standard library's http.ListenAndServe function:
+
+```go
+func main() {
+ handler := MyHTTPHandler()
+ manners.ListenAndServe(":7000", handler)
+}
+```
+
+Then, when you want to shut the server down:
+
+```go
+manners.Close()
+```
+
+(Note that this does not block until all the requests are finished. Rather, the call to manners.ListenAndServe will stop blocking when all the requests are finished.)
+
+Manners ensures that all requests are served by incrementing a WaitGroup when a request comes in and decrementing it when the request finishes.
+
+If your request handler spawns Goroutines that are not guaranteed to finish with the request, you can ensure they are also completed with the `StartRoutine` and `FinishRoutine` functions on the server.
+
+### Known Issues
+
+Manners does not correctly shut down long-lived keepalive connections when issued a shutdown command. Clients on an idle keepalive connection may see a connection reset error rather than a close. See https://github.com/braintree/manners/issues/13 for details.
+
+### Compatability
+
+Manners 0.3.0 and above uses standard library functionality introduced in Go 1.3.
+
+### Installation
+
+`go get github.com/braintree/manners`
diff --git a/vendor/github.com/braintree/manners/helpers_test.go b/vendor/github.com/braintree/manners/helpers_test.go
new file mode 100644
index 000000000..3c11a081d
--- /dev/null
+++ b/vendor/github.com/braintree/manners/helpers_test.go
@@ -0,0 +1,119 @@
+package manners
+
+import (
+ "bufio"
+ "crypto/tls"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "testing"
+)
+
+// a simple step-controllable http client
+type client struct {
+ tls bool
+ addr net.Addr
+ connected chan error
+ sendrequest chan bool
+ response chan *rawResponse
+ closed chan bool
+}
+
+type rawResponse struct {
+ body []string
+ err error
+}
+
+func (c *client) Run() {
+ go func() {
+ var err error
+ conn, err := net.Dial(c.addr.Network(), c.addr.String())
+ if err != nil {
+ c.connected <- err
+ return
+ }
+ if c.tls {
+ conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true})
+ }
+ c.connected <- nil
+ for <-c.sendrequest {
+ _, err = conn.Write([]byte("GET / HTTP/1.1\nHost: localhost:8000\n\n"))
+ if err != nil {
+ c.response <- &rawResponse{err: err}
+ }
+ // Read response; no content
+ scanner := bufio.NewScanner(conn)
+ var lines []string
+ for scanner.Scan() {
+ // our null handler doesn't send a body, so we know the request is
+ // done when we reach the blank line after the headers
+ line := scanner.Text()
+ if line == "" {
+ break
+ }
+ lines = append(lines, line)
+ }
+ c.response <- &rawResponse{lines, scanner.Err()}
+ }
+ conn.Close()
+ ioutil.ReadAll(conn)
+ c.closed <- true
+ }()
+}
+
+func newClient(addr net.Addr, tls bool) *client {
+ return &client{
+ addr: addr,
+ tls: tls,
+ connected: make(chan error),
+ sendrequest: make(chan bool),
+ response: make(chan *rawResponse),
+ closed: make(chan bool),
+ }
+}
+
+// a handler that returns 200 ok with no body
+var nullHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+func startGenericServer(t *testing.T, server *GracefulServer, statechanged chan http.ConnState, runner func() error) (l net.Listener, errc chan error) {
+ server.Addr = "localhost:0"
+ server.Handler = nullHandler
+ if statechanged != nil {
+ // Wrap the ConnState handler with something that will notify
+ // the statechanged channel when a state change happens
+ server.ConnState = func(conn net.Conn, newState http.ConnState) {
+ statechanged <- newState
+ }
+ }
+
+ server.up = make(chan net.Listener)
+ exitchan := make(chan error)
+
+ go func() {
+ exitchan <- runner()
+ }()
+
+ // wait for server socket to be bound
+ select {
+ case l = <-server.up:
+ // all good
+
+ case err := <-exitchan:
+ // all bad
+ t.Fatal("Server failed to start", err)
+ }
+ return l, exitchan
+}
+
+func startServer(t *testing.T, server *GracefulServer, statechanged chan http.ConnState) (
+ l net.Listener, errc chan error) {
+ return startGenericServer(t, server, statechanged, server.ListenAndServe)
+}
+
+func startTLSServer(t *testing.T, server *GracefulServer, certFile, keyFile string, statechanged chan http.ConnState) (l net.Listener, errc chan error) {
+ runner := func() error {
+ return server.ListenAndServeTLS(certFile, keyFile)
+ }
+
+ return startGenericServer(t, server, statechanged, runner)
+}
diff --git a/vendor/github.com/braintree/manners/interfaces.go b/vendor/github.com/braintree/manners/interfaces.go
new file mode 100644
index 000000000..fd0732857
--- /dev/null
+++ b/vendor/github.com/braintree/manners/interfaces.go
@@ -0,0 +1,7 @@
+package manners
+
+type waitGroup interface {
+ Add(int)
+ Done()
+ Wait()
+}
diff --git a/vendor/github.com/braintree/manners/server.go b/vendor/github.com/braintree/manners/server.go
new file mode 100644
index 000000000..dfd3b873b
--- /dev/null
+++ b/vendor/github.com/braintree/manners/server.go
@@ -0,0 +1,292 @@
+/*
+Package manners provides a wrapper for a standard net/http server that
+ensures all active HTTP client have completed their current request
+before the server shuts down.
+
+It can be used a drop-in replacement for the standard http package,
+or can wrap a pre-configured Server.
+
+eg.
+
+ http.Handle("/hello", func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("Hello\n"))
+ })
+
+ log.Fatal(manners.ListenAndServe(":8080", nil))
+
+or for a customized server:
+
+ s := manners.NewWithServer(&http.Server{
+ Addr: ":8080",
+ Handler: myHandler,
+ ReadTimeout: 10 * time.Second,
+ WriteTimeout: 10 * time.Second,
+ MaxHeaderBytes: 1 << 20,
+ })
+ log.Fatal(s.ListenAndServe())
+
+The server will shut down cleanly when the Close() method is called:
+
+ go func() {
+ sigchan := make(chan os.Signal, 1)
+ signal.Notify(sigchan, os.Interrupt, os.Kill)
+ <-sigchan
+ log.Info("Shutting down...")
+ manners.Close()
+ }()
+
+ http.Handle("/hello", myHandler)
+ log.Fatal(manners.ListenAndServe(":8080", nil))
+*/
+package manners
+
+import (
+ "crypto/tls"
+ "net"
+ "net/http"
+ "sync"
+ "sync/atomic"
+)
+
+// A GracefulServer maintains a WaitGroup that counts how many in-flight
+// requests the server is handling. When it receives a shutdown signal,
+// it stops accepting new requests but does not actually shut down until
+// all in-flight requests terminate.
+//
+// GracefulServer embeds the underlying net/http.Server making its non-override
+// methods and properties avaiable.
+//
+// It must be initialized by calling NewWithServer.
+type GracefulServer struct {
+ *http.Server
+
+ shutdown chan bool
+ shutdownFinished chan bool
+ wg waitGroup
+ routinesCount int
+
+ lcsmu sync.RWMutex
+ connections map[net.Conn]bool
+
+ up chan net.Listener // Only used by test code.
+}
+
+// NewServer creates a new GracefulServer.
+func NewServer() *GracefulServer {
+ return NewWithServer(new(http.Server))
+}
+
+// NewWithServer wraps an existing http.Server object and returns a
+// GracefulServer that supports all of the original Server operations.
+func NewWithServer(s *http.Server) *GracefulServer {
+ return &GracefulServer{
+ Server: s,
+ shutdown: make(chan bool),
+ shutdownFinished: make(chan bool, 1),
+ wg: new(sync.WaitGroup),
+ routinesCount: 0,
+ connections: make(map[net.Conn]bool),
+ }
+}
+
+// Close stops the server from accepting new requets and begins shutting down.
+// It returns true if it's the first time Close is called.
+func (s *GracefulServer) Close() bool {
+ return <-s.shutdown
+}
+
+// BlockingClose is similar to Close, except that it blocks until the last
+// connection has been closed.
+func (s *GracefulServer) BlockingClose() bool {
+ result := s.Close()
+ <-s.shutdownFinished
+ return result
+}
+
+// ListenAndServe provides a graceful equivalent of net/http.Serve.ListenAndServe.
+func (s *GracefulServer) ListenAndServe() error {
+ addr := s.Addr
+ if addr == "" {
+ addr = ":http"
+ }
+ listener, err := net.Listen("tcp", addr)
+ if err != nil {
+ return err
+ }
+
+ return s.Serve(listener)
+}
+
+// ListenAndServeTLS provides a graceful equivalent of net/http.Serve.ListenAndServeTLS.
+func (s *GracefulServer) ListenAndServeTLS(certFile, keyFile string) error {
+ // direct lift from net/http/server.go
+ addr := s.Addr
+ if addr == "" {
+ addr = ":https"
+ }
+ config := &tls.Config{}
+ if s.TLSConfig != nil {
+ *config = *s.TLSConfig
+ }
+ if config.NextProtos == nil {
+ config.NextProtos = []string{"http/1.1"}
+ }
+
+ var err error
+ config.Certificates = make([]tls.Certificate, 1)
+ config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
+ if err != nil {
+ return err
+ }
+
+ ln, err := net.Listen("tcp", addr)
+ if err != nil {
+ return err
+ }
+
+ return s.Serve(tls.NewListener(ln, config))
+}
+
+// Serve provides a graceful equivalent net/http.Server.Serve.
+func (s *GracefulServer) Serve(listener net.Listener) error {
+ // Wrap the server HTTP handler into graceful one, that will close kept
+ // alive connections if a new request is received after shutdown.
+ gracefulHandler := newGracefulHandler(s.Server.Handler)
+ s.Server.Handler = gracefulHandler
+
+ // Start a goroutine that waits for a shutdown signal and will stop the
+ // listener when it receives the signal. That in turn will result in
+ // unblocking of the http.Serve call.
+ go func() {
+ s.shutdown <- true
+ close(s.shutdown)
+ gracefulHandler.Close()
+ s.Server.SetKeepAlivesEnabled(false)
+ listener.Close()
+ }()
+
+ originalConnState := s.Server.ConnState
+
+ // s.ConnState is invoked by the net/http.Server every time a connection
+ // changes state. It keeps track of each connection's state over time,
+ // enabling manners to handle persisted connections correctly.
+ s.ConnState = func(conn net.Conn, newState http.ConnState) {
+ s.lcsmu.RLock()
+ protected := s.connections[conn]
+ s.lcsmu.RUnlock()
+
+ switch newState {
+
+ case http.StateNew:
+ // New connection -> StateNew
+ protected = true
+ s.StartRoutine()
+
+ case http.StateActive:
+ // (StateNew, StateIdle) -> StateActive
+ if gracefulHandler.IsClosed() {
+ conn.Close()
+ break
+ }
+
+ if !protected {
+ protected = true
+ s.StartRoutine()
+ }
+
+ default:
+ // (StateNew, StateActive) -> (StateIdle, StateClosed, StateHiJacked)
+ if protected {
+ s.FinishRoutine()
+ protected = false
+ }
+ }
+
+ s.lcsmu.Lock()
+ if newState == http.StateClosed || newState == http.StateHijacked {
+ delete(s.connections, conn)
+ } else {
+ s.connections[conn] = protected
+ }
+ s.lcsmu.Unlock()
+
+ if originalConnState != nil {
+ originalConnState(conn, newState)
+ }
+ }
+
+ // A hook to allow the server to notify others when it is ready to receive
+ // requests; only used by tests.
+ if s.up != nil {
+ s.up <- listener
+ }
+
+ err := s.Server.Serve(listener)
+ // An error returned on shutdown is not worth reporting.
+ if err != nil && gracefulHandler.IsClosed() {
+ err = nil
+ }
+
+ // Wait for pending requests to complete regardless the Serve result.
+ s.wg.Wait()
+ s.shutdownFinished <- true
+ return err
+}
+
+// StartRoutine increments the server's WaitGroup. Use this if a web request
+// starts more goroutines and these goroutines are not guaranteed to finish
+// before the request.
+func (s *GracefulServer) StartRoutine() {
+ s.lcsmu.Lock()
+ defer s.lcsmu.Unlock()
+ s.wg.Add(1)
+ s.routinesCount++
+}
+
+// FinishRoutine decrements the server's WaitGroup. Use this to complement
+// StartRoutine().
+func (s *GracefulServer) FinishRoutine() {
+ s.lcsmu.Lock()
+ defer s.lcsmu.Unlock()
+ s.wg.Done()
+ s.routinesCount--
+}
+
+// RoutinesCount returns the number of currently running routines
+func (s *GracefulServer) RoutinesCount() int {
+ s.lcsmu.RLock()
+ defer s.lcsmu.RUnlock()
+ return s.routinesCount
+}
+
+// gracefulHandler is used by GracefulServer to prevent calling ServeHTTP on
+// to be closed kept-alive connections during the server shutdown.
+type gracefulHandler struct {
+ closed int32 // accessed atomically.
+ wrapped http.Handler
+}
+
+func newGracefulHandler(wrapped http.Handler) *gracefulHandler {
+ return &gracefulHandler{
+ wrapped: wrapped,
+ }
+}
+
+func (gh *gracefulHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if atomic.LoadInt32(&gh.closed) == 0 {
+ gh.wrapped.ServeHTTP(w, r)
+ return
+ }
+ r.Body.Close()
+ // Server is shutting down at this moment, and the connection that this
+ // handler is being called on is about to be closed. So we do not need to
+ // actually execute the handler logic.
+}
+
+func (gh *gracefulHandler) Close() {
+ atomic.StoreInt32(&gh.closed, 1)
+}
+
+func (gh *gracefulHandler) IsClosed() bool {
+ return atomic.LoadInt32(&gh.closed) == 1
+}
diff --git a/vendor/github.com/braintree/manners/server_test.go b/vendor/github.com/braintree/manners/server_test.go
new file mode 100644
index 000000000..994284216
--- /dev/null
+++ b/vendor/github.com/braintree/manners/server_test.go
@@ -0,0 +1,289 @@
+package manners
+
+import (
+ "net"
+ "net/http"
+ "testing"
+ "time"
+
+ helpers "github.com/braintree/manners/test_helpers"
+)
+
+type httpInterface interface {
+ ListenAndServe() error
+ ListenAndServeTLS(certFile, keyFile string) error
+ Serve(listener net.Listener) error
+}
+
+// Test that the method signatures of the methods we override from net/http/Server match those of the original.
+func TestInterface(t *testing.T) {
+ var original, ours interface{}
+ original = &http.Server{}
+ ours = &GracefulServer{}
+ if _, ok := original.(httpInterface); !ok {
+ t.Errorf("httpInterface definition does not match the canonical server!")
+ }
+ if _, ok := ours.(httpInterface); !ok {
+ t.Errorf("GracefulServer does not implement httpInterface")
+ }
+}
+
+// Tests that the server allows in-flight requests to complete
+// before shutting down.
+func TestGracefulness(t *testing.T) {
+ server := NewServer()
+ wg := helpers.NewWaitGroup()
+ server.wg = wg
+ statechanged := make(chan http.ConnState)
+ listener, exitchan := startServer(t, server, statechanged)
+
+ client := newClient(listener.Addr(), false)
+ client.Run()
+
+ // wait for client to connect, but don't let it send the request yet
+ if err := <-client.connected; err != nil {
+ t.Fatal("Client failed to connect to server", err)
+ }
+ // Even though the client is connected, the server ConnState handler may
+ // not know about that yet. So wait until it is called.
+ waitForState(t, statechanged, http.StateNew, "Request not received")
+
+ server.Close()
+
+ waiting := <-wg.WaitCalled
+ if waiting < 1 {
+ t.Errorf("Expected the waitgroup to equal 1 at shutdown; actually %d", waiting)
+ }
+
+ // allow the client to finish sending the request and make sure the server exits after
+ // (client will be in connected but idle state at that point)
+ client.sendrequest <- true
+ close(client.sendrequest)
+ if err := <-exitchan; err != nil {
+ t.Error("Unexpected error during shutdown", err)
+ }
+}
+
+// Tests that starting the server and closing in 2 new, separate goroutines doesnot
+// get flagged by the race detector (need to run 'go test' w/the -race flag)
+func TestRacyClose(t *testing.T) {
+ go func() {
+ ListenAndServe(":9000", nil)
+ }()
+
+ go func() {
+ Close()
+ }()
+}
+
+// Tests that the server begins to shut down when told to and does not accept
+// new requests once shutdown has begun
+func TestShutdown(t *testing.T) {
+ server := NewServer()
+ wg := helpers.NewWaitGroup()
+ server.wg = wg
+ statechanged := make(chan http.ConnState)
+ listener, exitchan := startServer(t, server, statechanged)
+
+ client1 := newClient(listener.Addr(), false)
+ client1.Run()
+
+ // wait for client1 to connect
+ if err := <-client1.connected; err != nil {
+ t.Fatal("Client failed to connect to server", err)
+ }
+ // Even though the client is connected, the server ConnState handler may
+ // not know about that yet. So wait until it is called.
+ waitForState(t, statechanged, http.StateNew, "Request not received")
+
+ // start the shutdown; once it hits waitgroup.Wait()
+ // the listener should of been closed, though client1 is still connected
+ if server.Close() != true {
+ t.Fatal("first call to Close returned false")
+ }
+ if server.Close() != false {
+ t.Fatal("second call to Close returned true")
+ }
+
+ waiting := <-wg.WaitCalled
+ if waiting != 1 {
+ t.Errorf("Waitcount should be one, got %d", waiting)
+ }
+
+ // should get connection refused at this point
+ client2 := newClient(listener.Addr(), false)
+ client2.Run()
+
+ if err := <-client2.connected; err == nil {
+ t.Fatal("client2 connected when it should of received connection refused")
+ }
+
+ // let client1 finish so the server can exit
+ close(client1.sendrequest) // don't bother sending an actual request
+
+ <-exitchan
+}
+
+// If a request is sent to a closed server via a kept alive connection then
+// the server closes the connection upon receiving the request.
+func TestRequestAfterClose(t *testing.T) {
+ // Given
+ server := NewServer()
+ srvStateChangedCh := make(chan http.ConnState, 100)
+ listener, srvClosedCh := startServer(t, server, srvStateChangedCh)
+
+ client := newClient(listener.Addr(), false)
+ client.Run()
+ <-client.connected
+ client.sendrequest <- true
+ <-client.response
+
+ server.Close()
+ if err := <-srvClosedCh; err != nil {
+ t.Error("Unexpected error during shutdown", err)
+ }
+
+ // When
+ client.sendrequest <- true
+ rr := <-client.response
+
+ // Then
+ if rr.body != nil || rr.err != nil {
+ t.Errorf("Request should be rejected, body=%v, err=%v", rr.body, rr.err)
+ }
+}
+
+func waitForState(t *testing.T, waiter chan http.ConnState, state http.ConnState, errmsg string) {
+ for {
+ select {
+ case ns := <-waiter:
+ if ns == state {
+ return
+ }
+ case <-time.After(time.Second):
+ t.Fatal(errmsg)
+ }
+ }
+}
+
+// Test that a request moving from active->idle->active using an actual
+// network connection still results in a corect shutdown
+func TestStateTransitionActiveIdleActive(t *testing.T) {
+ server := NewServer()
+ wg := helpers.NewWaitGroup()
+ statechanged := make(chan http.ConnState)
+ server.wg = wg
+ listener, exitchan := startServer(t, server, statechanged)
+
+ client := newClient(listener.Addr(), false)
+ client.Run()
+
+ // wait for client to connect, but don't let it send the request
+ if err := <-client.connected; err != nil {
+ t.Fatal("Client failed to connect to server", err)
+ }
+
+ for i := 0; i < 2; i++ {
+ client.sendrequest <- true
+ waitForState(t, statechanged, http.StateActive, "Client failed to reach active state")
+ <-client.response
+ waitForState(t, statechanged, http.StateIdle, "Client failed to reach idle state")
+ }
+
+ // client is now in an idle state
+
+ server.Close()
+ waiting := <-wg.WaitCalled
+ if waiting != 0 {
+ t.Errorf("Waitcount should be zero, got %d", waiting)
+ }
+
+ if err := <-exitchan; err != nil {
+ t.Error("Unexpected error during shutdown", err)
+ }
+}
+
+// Test state transitions from new->active->-idle->closed using an actual
+// network connection and make sure the waitgroup count is correct at the end.
+func TestStateTransitionActiveIdleClosed(t *testing.T) {
+ var (
+ listener net.Listener
+ exitchan chan error
+ )
+
+ keyFile, err1 := helpers.NewTempFile(helpers.Key)
+ certFile, err2 := helpers.NewTempFile(helpers.Cert)
+ defer keyFile.Unlink()
+ defer certFile.Unlink()
+
+ if err1 != nil || err2 != nil {
+ t.Fatal("Failed to create temporary files", err1, err2)
+ }
+
+ for _, withTLS := range []bool{false, true} {
+ server := NewServer()
+ wg := helpers.NewWaitGroup()
+ statechanged := make(chan http.ConnState)
+ server.wg = wg
+ if withTLS {
+ listener, exitchan = startTLSServer(t, server, certFile.Name(), keyFile.Name(), statechanged)
+ } else {
+ listener, exitchan = startServer(t, server, statechanged)
+ }
+
+ client := newClient(listener.Addr(), withTLS)
+ client.Run()
+
+ // wait for client to connect, but don't let it send the request
+ if err := <-client.connected; err != nil {
+ t.Fatal("Client failed to connect to server", err)
+ }
+
+ client.sendrequest <- true
+ waitForState(t, statechanged, http.StateActive, "Client failed to reach active state")
+
+ rr := <-client.response
+ if rr.err != nil {
+ t.Fatalf("tls=%t unexpected error from client %s", withTLS, rr.err)
+ }
+
+ waitForState(t, statechanged, http.StateIdle, "Client failed to reach idle state")
+
+ // client is now in an idle state
+ close(client.sendrequest)
+ <-client.closed
+ waitForState(t, statechanged, http.StateClosed, "Client failed to reach closed state")
+
+ server.Close()
+ waiting := <-wg.WaitCalled
+ if waiting != 0 {
+ t.Errorf("Waitcount should be zero, got %d", waiting)
+ }
+
+ if err := <-exitchan; err != nil {
+ t.Error("Unexpected error during shutdown", err)
+ }
+ }
+}
+
+func TestRoutinesCount(t *testing.T) {
+ var count int
+ server := NewServer()
+
+ count = server.RoutinesCount()
+ if count != 0 {
+ t.Errorf("Expected the routines count to equal 0; actually %d", count)
+ }
+
+ server.StartRoutine()
+ count = server.RoutinesCount()
+ if count != 1 {
+ t.Errorf("Expected the routines count to equal 1; actually %d", count)
+ }
+
+ server.FinishRoutine()
+ count = server.RoutinesCount()
+ if count != 0 {
+ t.Errorf("Expected the routines count to equal 0; actually %d", count)
+ }
+}
diff --git a/vendor/github.com/braintree/manners/static.go b/vendor/github.com/braintree/manners/static.go
new file mode 100644
index 000000000..b53950675
--- /dev/null
+++ b/vendor/github.com/braintree/manners/static.go
@@ -0,0 +1,47 @@
+package manners
+
+import (
+ "net"
+ "net/http"
+ "sync"
+)
+
+var (
+ defaultServer *GracefulServer
+ defaultServerLock = &sync.Mutex{}
+)
+
+func init() {
+ defaultServerLock.Lock()
+}
+
+// ListenAndServe provides a graceful version of the function provided by the
+// net/http package. Call Close() to stop the server.
+func ListenAndServe(addr string, handler http.Handler) error {
+ defaultServer = NewWithServer(&http.Server{Addr: addr, Handler: handler})
+ defaultServerLock.Unlock()
+ return defaultServer.ListenAndServe()
+}
+
+// ListenAndServeTLS provides a graceful version of the function provided by the
+// net/http package. Call Close() to stop the server.
+func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
+ defaultServer = NewWithServer(&http.Server{Addr: addr, Handler: handler})
+ defaultServerLock.Unlock()
+ return defaultServer.ListenAndServeTLS(certFile, keyFile)
+}
+
+// Serve provides a graceful version of the function provided by the net/http
+// package. Call Close() to stop the server.
+func Serve(l net.Listener, handler http.Handler) error {
+ defaultServer = NewWithServer(&http.Server{Handler: handler})
+ defaultServerLock.Unlock()
+ return defaultServer.Serve(l)
+}
+
+// Shuts down the default server used by ListenAndServe, ListenAndServeTLS and
+// Serve. It returns true if it's the first time Close is called.
+func Close() bool {
+ defaultServerLock.Lock()
+ return defaultServer.Close()
+}
diff --git a/vendor/github.com/braintree/manners/test_helpers/certs.go b/vendor/github.com/braintree/manners/test_helpers/certs.go
new file mode 100644
index 000000000..ede248b3d
--- /dev/null
+++ b/vendor/github.com/braintree/manners/test_helpers/certs.go
@@ -0,0 +1,29 @@
+package test_helpers
+
+// 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/pkg/crypto/tls:
+// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
+var (
+ Cert = []byte(`-----BEGIN CERTIFICATE-----
+MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
+bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
+bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa
+IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA
+AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
+EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
+AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk
+Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA==
+-----END CERTIFICATE-----`)
+
+ Key = []byte(`-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0
+0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV
+NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d
+AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW
+MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD
+EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA
+1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE=
+-----END RSA PRIVATE KEY-----`)
+)
diff --git a/vendor/github.com/braintree/manners/test_helpers/conn.go b/vendor/github.com/braintree/manners/test_helpers/conn.go
new file mode 100644
index 000000000..8c610f58e
--- /dev/null
+++ b/vendor/github.com/braintree/manners/test_helpers/conn.go
@@ -0,0 +1,13 @@
+package test_helpers
+
+import "net"
+
+type Conn struct {
+ net.Conn
+ CloseCalled bool
+}
+
+func (c *Conn) Close() error {
+ c.CloseCalled = true
+ return nil
+}
diff --git a/vendor/github.com/braintree/manners/test_helpers/listener.go b/vendor/github.com/braintree/manners/test_helpers/listener.go
new file mode 100644
index 000000000..e3af35a6e
--- /dev/null
+++ b/vendor/github.com/braintree/manners/test_helpers/listener.go
@@ -0,0 +1,34 @@
+package test_helpers
+
+import (
+ "errors"
+ "net"
+)
+
+type Listener struct {
+ AcceptRelease chan bool
+ CloseCalled chan bool
+}
+
+func NewListener() *Listener {
+ return &Listener{
+ make(chan bool, 1),
+ make(chan bool, 1),
+ }
+}
+
+func (l *Listener) Addr() net.Addr {
+ addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
+ return addr
+}
+
+func (l *Listener) Close() error {
+ l.CloseCalled <- true
+ l.AcceptRelease <- true
+ return nil
+}
+
+func (l *Listener) Accept() (net.Conn, error) {
+ <-l.AcceptRelease
+ return nil, errors.New("connection closed")
+}
diff --git a/vendor/github.com/braintree/manners/test_helpers/temp_file.go b/vendor/github.com/braintree/manners/test_helpers/temp_file.go
new file mode 100644
index 000000000..c4aa263a0
--- /dev/null
+++ b/vendor/github.com/braintree/manners/test_helpers/temp_file.go
@@ -0,0 +1,27 @@
+package test_helpers
+
+import (
+ "io/ioutil"
+ "os"
+)
+
+type TempFile struct {
+ *os.File
+}
+
+func NewTempFile(content []byte) (*TempFile, error) {
+ f, err := ioutil.TempFile("", "graceful-test")
+ if err != nil {
+ return nil, err
+ }
+
+ f.Write(content)
+ return &TempFile{f}, nil
+}
+
+func (tf *TempFile) Unlink() {
+ if tf.File != nil {
+ os.Remove(tf.Name())
+ tf.File = nil
+ }
+}
diff --git a/vendor/github.com/braintree/manners/test_helpers/wait_group.go b/vendor/github.com/braintree/manners/test_helpers/wait_group.go
new file mode 100644
index 000000000..1df590db7
--- /dev/null
+++ b/vendor/github.com/braintree/manners/test_helpers/wait_group.go
@@ -0,0 +1,33 @@
+package test_helpers
+
+import "sync"
+
+type WaitGroup struct {
+ sync.Mutex
+ Count int
+ WaitCalled chan int
+}
+
+func NewWaitGroup() *WaitGroup {
+ return &WaitGroup{
+ WaitCalled: make(chan int, 1),
+ }
+}
+
+func (wg *WaitGroup) Add(delta int) {
+ wg.Lock()
+ wg.Count++
+ wg.Unlock()
+}
+
+func (wg *WaitGroup) Done() {
+ wg.Lock()
+ wg.Count--
+ wg.Unlock()
+}
+
+func (wg *WaitGroup) Wait() {
+ wg.Lock()
+ wg.WaitCalled <- wg.Count
+ wg.Unlock()
+}
diff --git a/vendor/github.com/braintree/manners/transition_test.go b/vendor/github.com/braintree/manners/transition_test.go
new file mode 100644
index 000000000..5d398514e
--- /dev/null
+++ b/vendor/github.com/braintree/manners/transition_test.go
@@ -0,0 +1,54 @@
+package manners
+
+import (
+ helpers "github.com/braintree/manners/test_helpers"
+ "net/http"
+ "strings"
+ "testing"
+)
+
+func TestStateTransitions(t *testing.T) {
+ tests := []transitionTest{
+ transitionTest{[]http.ConnState{http.StateNew, http.StateActive}, 1},
+ transitionTest{[]http.ConnState{http.StateNew, http.StateClosed}, 0},
+ transitionTest{[]http.ConnState{http.StateNew, http.StateActive, http.StateClosed}, 0},
+ transitionTest{[]http.ConnState{http.StateNew, http.StateActive, http.StateHijacked}, 0},
+ transitionTest{[]http.ConnState{http.StateNew, http.StateActive, http.StateIdle}, 0},
+ transitionTest{[]http.ConnState{http.StateNew, http.StateActive, http.StateIdle, http.StateActive}, 1},
+ transitionTest{[]http.ConnState{http.StateNew, http.StateActive, http.StateIdle, http.StateActive, http.StateIdle}, 0},
+ transitionTest{[]http.ConnState{http.StateNew, http.StateActive, http.StateIdle, http.StateActive, http.StateClosed}, 0},
+ transitionTest{[]http.ConnState{http.StateNew, http.StateActive, http.StateIdle, http.StateActive, http.StateIdle, http.StateClosed}, 0},
+ }
+
+ for _, test := range tests {
+ testStateTransition(t, test)
+ }
+}
+
+type transitionTest struct {
+ states []http.ConnState
+ expectedWgCount int
+}
+
+func testStateTransition(t *testing.T, test transitionTest) {
+ server := NewServer()
+ wg := helpers.NewWaitGroup()
+ server.wg = wg
+ startServer(t, server, nil)
+
+ conn := &helpers.Conn{}
+ for _, newState := range test.states {
+ server.ConnState(conn, newState)
+ }
+
+ server.Close()
+ waiting := <-wg.WaitCalled
+ if waiting != test.expectedWgCount {
+ names := make([]string, len(test.states))
+ for i, s := range test.states {
+ names[i] = s.String()
+ }
+ transitions := strings.Join(names, " -> ")
+ t.Errorf("%s - Waitcount should be %d, got %d", transitions, test.expectedWgCount, waiting)
+ }
+}