From cf7a05f80f68b5b1c8bcc0089679dd497cec2506 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Sun, 14 Jun 2015 23:53:32 -0800 Subject: first commit --- .../src/github.com/braintree/manners/LICENSE | 19 +++++ .../src/github.com/braintree/manners/README.md | 33 +++++++++ .../github.com/braintree/manners/helper_test.go | 34 +++++++++ .../src/github.com/braintree/manners/listener.go | 49 +++++++++++++ .../src/github.com/braintree/manners/server.go | 83 ++++++++++++++++++++++ .../github.com/braintree/manners/server_test.go | 71 ++++++++++++++++++ 6 files changed, 289 insertions(+) create mode 100644 Godeps/_workspace/src/github.com/braintree/manners/LICENSE create mode 100644 Godeps/_workspace/src/github.com/braintree/manners/README.md create mode 100644 Godeps/_workspace/src/github.com/braintree/manners/helper_test.go create mode 100644 Godeps/_workspace/src/github.com/braintree/manners/listener.go create mode 100644 Godeps/_workspace/src/github.com/braintree/manners/server.go create mode 100644 Godeps/_workspace/src/github.com/braintree/manners/server_test.go (limited to 'Godeps/_workspace/src/github.com/braintree/manners') 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/README.md b/Godeps/_workspace/src/github.com/braintree/manners/README.md new file mode 100644 index 000000000..8c9a239b4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/braintree/manners/README.md @@ -0,0 +1,33 @@ +# 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() + server := manners.NewServer() + server.ListenAndServe(":7000", handler) +} +``` + +Then, when you want to shut the server down: + +```go +server.Shutdown <- true +``` + +(Note that this does not block until all the requests are finished. Rather, the call to server.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. + +### 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/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.") + } +} -- cgit v1.2.3-1-g7c22