summaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/github.com/braintree
diff options
context:
space:
mode:
Diffstat (limited to 'Godeps/_workspace/src/github.com/braintree')
-rw-r--r--Godeps/_workspace/src/github.com/braintree/manners/LICENSE19
-rw-r--r--Godeps/_workspace/src/github.com/braintree/manners/helper_test.go34
-rw-r--r--Godeps/_workspace/src/github.com/braintree/manners/listener.go49
-rw-r--r--Godeps/_workspace/src/github.com/braintree/manners/server.go83
-rw-r--r--Godeps/_workspace/src/github.com/braintree/manners/server_test.go71
5 files changed, 256 insertions, 0 deletions
diff --git a/Godeps/_workspace/src/github.com/braintree/manners/LICENSE b/Godeps/_workspace/src/github.com/braintree/manners/LICENSE
new file mode 100644
index 000000000..91ef5beed
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/braintree/manners/helper_test.go b/Godeps/_workspace/src/github.com/braintree/manners/helper_test.go
new file mode 100644
index 000000000..ea721a180
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/braintree/manners/helper_test.go
@@ -0,0 +1,34 @@
+package manners
+
+import (
+ "net/http"
+ "time"
+)
+
+// A response handler that blocks until it receives a signal; simulates an
+// arbitrarily long web request. The "ready" channel is to prevent a race
+// condition in the test where the test moves on before the server is ready
+// to handle the request.
+func newBlockingHandler(ready, done chan bool) *blockingHandler {
+ return &blockingHandler{ready, done}
+}
+
+type blockingHandler struct {
+ ready chan bool
+ done chan bool
+}
+
+func (h *blockingHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+ h.ready <- true
+ time.Sleep(1e2)
+ h.done <- true
+}
+
+// A response handler that does nothing.
+func newTestHandler() testHandler {
+ return testHandler{}
+}
+
+type testHandler struct{}
+
+func (h testHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {}
diff --git a/Godeps/_workspace/src/github.com/braintree/manners/listener.go b/Godeps/_workspace/src/github.com/braintree/manners/listener.go
new file mode 100644
index 000000000..dd84e4a2e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/braintree/manners/listener.go
@@ -0,0 +1,49 @@
+package manners
+
+import (
+ "net"
+ "sync"
+)
+
+func NewListener(l net.Listener, s *GracefulServer) *GracefulListener {
+ return &GracefulListener{l, true, s, sync.RWMutex{}}
+}
+
+// A GracefulListener differs from a standard net.Listener in one way: if
+// Accept() is called after it is gracefully closed, it returns a
+// listenerAlreadyClosed error. The GracefulServer will ignore this
+// error.
+type GracefulListener struct {
+ net.Listener
+ open bool
+ server *GracefulServer
+ rw sync.RWMutex
+}
+
+func (l *GracefulListener) Accept() (net.Conn, error) {
+ conn, err := l.Listener.Accept()
+ if err != nil {
+ l.rw.RLock()
+ defer l.rw.RUnlock()
+ if !l.open {
+ err = listenerAlreadyClosed{err}
+ }
+ return nil, err
+ }
+ return conn, nil
+}
+
+func (l *GracefulListener) Close() error {
+ l.rw.Lock()
+ defer l.rw.Unlock()
+ if !l.open {
+ return nil
+ }
+ l.open = false
+ err := l.Listener.Close()
+ return err
+}
+
+type listenerAlreadyClosed struct {
+ error
+}
diff --git a/Godeps/_workspace/src/github.com/braintree/manners/server.go b/Godeps/_workspace/src/github.com/braintree/manners/server.go
new file mode 100644
index 000000000..a79246668
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/braintree/manners/server.go
@@ -0,0 +1,83 @@
+package manners
+
+import (
+ "net"
+ "net/http"
+ "sync"
+)
+
+// Creates a new GracefulServer. The server will begin shutting down when
+// a value is passed to the Shutdown channel.
+func NewServer() *GracefulServer {
+ return &GracefulServer{
+ Shutdown: make(chan bool),
+ }
+}
+
+// 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.
+type GracefulServer struct {
+ Shutdown chan bool
+ wg sync.WaitGroup
+ shutdownHandler func()
+ InnerServer http.Server
+}
+
+// A helper function that emulates the functionality of http.ListenAndServe.
+func (s *GracefulServer) ListenAndServe(addr string, handler http.Handler) error {
+ oldListener, err := net.Listen("tcp", addr)
+ if err != nil {
+ return err
+ }
+
+ listener := NewListener(oldListener, s)
+ err = s.Serve(listener, handler)
+ return err
+}
+
+// Similar to http.Serve. The listener passed must wrap a GracefulListener.
+func (s *GracefulServer) Serve(listener net.Listener, handler http.Handler) error {
+ s.shutdownHandler = func() { listener.Close() }
+ s.listenForShutdown()
+ s.InnerServer.Handler = handler
+ s.InnerServer.ConnState = func(conn net.Conn, newState http.ConnState) {
+ switch newState {
+ case http.StateNew:
+ s.StartRoutine()
+ case http.StateClosed, http.StateHijacked:
+ s.FinishRoutine()
+ }
+ }
+ err := s.InnerServer.Serve(listener)
+
+ // This block is reached when the server has received a shut down command.
+ if err == nil {
+ s.wg.Wait()
+ return nil
+ } else if _, ok := err.(listenerAlreadyClosed); ok {
+ s.wg.Wait()
+ return nil
+ }
+ return err
+}
+
+// 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.wg.Add(1)
+}
+
+// Decrement the server's WaitGroup. Used this to complement StartRoutine().
+func (s *GracefulServer) FinishRoutine() {
+ s.wg.Done()
+}
+
+func (s *GracefulServer) listenForShutdown() {
+ go func() {
+ <-s.Shutdown
+ s.shutdownHandler()
+ }()
+}
diff --git a/Godeps/_workspace/src/github.com/braintree/manners/server_test.go b/Godeps/_workspace/src/github.com/braintree/manners/server_test.go
new file mode 100644
index 000000000..0da015566
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/braintree/manners/server_test.go
@@ -0,0 +1,71 @@
+package manners
+
+import (
+ "net/http"
+ "testing"
+)
+
+// Tests that the server allows in-flight requests to complete before shutting
+// down.
+func TestGracefulness(t *testing.T) {
+ ready := make(chan bool)
+ done := make(chan bool)
+
+ exited := false
+
+ handler := newBlockingHandler(ready, done)
+ server := NewServer()
+
+ go func() {
+ err := server.ListenAndServe(":7000", handler)
+ if err != nil {
+ t.Error(err)
+ }
+
+ exited = true
+ }()
+
+ go func() {
+ _, err := http.Get("http://localhost:7000")
+ if err != nil {
+ t.Error(err)
+ }
+ }()
+
+ // This will block until the server is inside the handler function.
+ <-ready
+
+ server.Shutdown <- true
+ <-done
+
+ if exited {
+ t.Fatal("The request did not complete before server exited")
+ } else {
+ // The handler is being allowed to run to completion; test passes.
+ }
+}
+
+// Tests that the server begins to shut down when told to and does not accept
+// new requests
+func TestShutdown(t *testing.T) {
+ handler := newTestHandler()
+ server := NewServer()
+ exited := make(chan bool)
+
+ go func() {
+ err := server.ListenAndServe(":7100", handler)
+ if err != nil {
+ t.Error(err)
+ }
+ exited <- true
+ }()
+
+ server.Shutdown <- true
+
+ <-exited
+ _, err := http.Get("http://localhost:7100")
+
+ if err == nil {
+ t.Fatal("Did not receive an error when trying to connect to server.")
+ }
+}