Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2302939
Merge pull request #1 from mailgun/non-backwards-compat
klizhentas Aug 23, 2014
9b10f8a
Implementation with race condition
Aug 25, 2014
42e5572
Fix corrupted socket
Aug 27, 2014
700effe
Expose keep alive listener
Aug 31, 2014
1d26832
Add support for TLS updates
Aug 31, 2014
463f1fe
Always preserve listener
Sep 4, 2014
4213b9d
Support hijacking of TLS listeners
Sep 5, 2014
a592baf
Add method for explicit listener
Sep 8, 2014
bba6293
Add advanced state transitions handler
Sep 21, 2014
69a4adc
Add GetFile method
Sep 21, 2014
38e66a3
expose waitgroup.Wait so shutdown handlers can wait on the server
hollow Dec 3, 2014
296f3b8
Merge pull request #2 from remerge/master
klizhentas Dec 19, 2014
4c2f6a9
Revert "expose waitgroup.Wait so shutdown handlers can wait on the se…
klizhentas Jan 7, 2015
f6cce01
Merge pull request #3 from mailgun/revert-2-master
klizhentas Jan 7, 2015
1530288
Fix missing TLS state
klizhentas Jan 7, 2015
97d9cc9
Code review comments
klizhentas Jan 8, 2015
12f1b91
Merge pull request #4 from mailgun/alexander/tls
klizhentas Jan 19, 2015
b254a48
Added support for net/http/fcgi listeners.
Feb 8, 2015
77b2c73
New CloseOnInterrupt function provides an easy API for registering OS…
Feb 8, 2015
15039a4
Correction.
Feb 8, 2015
b61da14
Added more description of FCGI usage.
Feb 9, 2015
ec92c6e
Improved the readme.
Feb 9, 2015
7bb2fc6
Improved the Unix/Fcgi behaviour; added logging on startup & shutdown
Feb 9, 2015
bd32029
Removed erroneous type assertion.
Feb 9, 2015
5600a06
doc comments
Feb 9, 2015
374dba5
Merged in current head of braintree/manners/master, usingstrategy=theirs
Jun 11, 2015
f1eb01c
Manually merged forward all the FCGI code, along with the pluggable l…
Jun 11, 2015
cdce84f
Refinement and bug-fixing on interrupt handling code. Extra checks ha…
Jun 11, 2015
27a08b6
Merged new FCGI and logging into the tip of mailgun/manners:master.
Aug 18, 2016
6dca054
Added missing logging info
Oct 12, 2016
d108ea5
deps
Nov 16, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,47 @@ Manners ensures that all requests are served by incrementing a WaitGroup when a

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.

### HTTP, HTTPS and FCGI

Manners supports three protocols: HTTP, HTTPS and FCGI. HTTP is illustrated above.
For HTTPS, Manners can likewise act as a drop-in replacement for the standard library's
[http.ListenAndServeTLS](http://golang.org/pkg/net/http/#ListenAndServeTLS) function:

```go
func main() {
handler := MyHTTPHandler()
certFile := MyCertificate()
keyFile := MyKeyFile()
manners.ListenAndServeTLS(":https", certFile, keyFile, handler)
}
```

In Manners, FCGI only operates via local a Unix socket connected to a co-hosted proxy, such as Apache or Nginx.

```go
func main() {
handler := MyHTTPHandler()
manners.ListenAndServe("/var/run/goserver.sock", handler)
}
```

To use FCGI, the port string must specify the Unix socket and start with a slash or dot, as in the example above. In this case, Manners will use [fcgi.Serve](http://golang.org/pkg/net/http/fcgi/#Serve).

In each of the protocols, Manners drains down the connections cleanly when `manners.Close()` is called.

### Handling signals

It's good to close down the server cleanly when OS signals are received. This is easy: just add

```go
manners.CloseOnInterrupt()
```
before the `ListenAndServe` call. This kicks off a separate goroutine to wait for an OS signal, upon which it simply calls `manners.Close()` for you. Optionally, you can pass in a list of the particular signals you care about and you can find out which signal was received, if any, afterwards.

### 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.
Expand Down
27 changes: 14 additions & 13 deletions helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ import (
"testing"
)

func newServer() *GracefulServer {
return NewWithServer(new(http.Server))
}

// a simple step-controllable http client
type client struct {
tls bool
addr net.Addr
connected chan error
sendrequest chan bool
idle chan error
idlerelease chan bool
response chan *rawResponse
closed chan bool
}

type rawResponse struct {
body []string
err error
}

func (c *client) Run() {
go func() {
var err error
Expand All @@ -39,19 +39,21 @@ func (c *client) Run() {
for <-c.sendrequest {
_, err = conn.Write([]byte("GET / HTTP/1.1\nHost: localhost:8000\n\n"))
if err != nil {
c.idle <- err
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
if scanner.Text() == "" {
line := scanner.Text()
if line == "" {
break
}
lines = append(lines, line)
}
c.idle <- scanner.Err()
<-c.idlerelease
c.response <- &rawResponse{lines, scanner.Err()}
}
conn.Close()
ioutil.ReadAll(conn)
Expand All @@ -65,8 +67,7 @@ func newClient(addr net.Addr, tls bool) *client {
tls: tls,
connected: make(chan error),
sendrequest: make(chan bool),
idle: make(chan error),
idlerelease: make(chan bool),
response: make(chan *rawResponse),
closed: make(chan bool),
}
}
Expand All @@ -81,7 +82,7 @@ func startGenericServer(t *testing.T, server *GracefulServer, statechanged chan
// 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
statechanged <- conn.LocalAddr().(*gracefulAddr).gconn.lastHTTPState
}
}

Expand Down
190 changes: 190 additions & 0 deletions listener.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package manners

import (
"crypto/tls"
"fmt"
"net"
"net/http"
"os"
"sync"
"time"
)

// NewListener wraps an existing listener for use with
// GracefulServer.
//
// Note that you generally don't need to use this directly as
// GracefulServer will automatically wrap any non-graceful listeners
// supplied to it.
func NewListener(l net.Listener) *GracefulListener {
return &GracefulListener{
listener: l,
mutex: &sync.RWMutex{},
open: true,
}
}

// A gracefulCon wraps a normal net.Conn and tracks the last known http state.
type gracefulConn struct {
net.Conn
lastHTTPState http.ConnState
// protected tells whether the connection is going to defer server shutdown
// until the current HTTP request is completed.
protected bool
}

type gracefulAddr struct {
net.Addr
gconn *gracefulConn
}

func (g *gracefulConn) LocalAddr() net.Addr {
return &gracefulAddr{g.Conn.LocalAddr(), g}
}

// retrieveGracefulConn retrieves a concrete gracefulConn instance from an
// interface value that can either refer to it directly or refer to a tls.Conn
// instance wrapping around a gracefulConn one.
func retrieveGracefulConn(conn net.Conn) *gracefulConn {
return conn.LocalAddr().(*gracefulAddr).gconn
}

// 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 {
listener net.Listener
open bool
mutex *sync.RWMutex
}

func (l *GracefulListener) isClosed() bool {
l.mutex.RLock()
defer l.mutex.RUnlock()
return !l.open
}

func (l *GracefulListener) Addr() net.Addr {
return l.listener.Addr()
}

// Accept implements the Accept method in the Listener interface.
func (l *GracefulListener) Accept() (net.Conn, error) {
conn, err := l.listener.Accept()
if err != nil {
if l.isClosed() {
err = listenerAlreadyClosed{err}
}
return nil, err
}

// don't wrap connection if it's tls so we won't break
// http server internal logic that relies on the type
if _, ok := conn.(*tls.Conn); ok {
return conn, nil
}
return &gracefulConn{Conn: conn}, nil
}

// Close tells the wrapped listener to stop listening. It is idempotent.
func (l *GracefulListener) Close() error {
l.mutex.Lock()
defer l.mutex.Unlock()
if !l.open {
return nil
}
l.open = false
return l.listener.Close()
}

func (l *GracefulListener) GetFile() (*os.File, error) {
return getListenerFile(l.listener)
}

func (l *GracefulListener) Clone() (net.Listener, error) {
l.mutex.Lock()
defer l.mutex.Unlock()

if !l.open {
return nil, fmt.Errorf("listener is already closed")
}

file, err := l.GetFile()
if err != nil {
return nil, err
}
defer file.Close()

fl, err := net.FileListener(file)
if nil != err {
return nil, err
}
return fl, nil
}

// A listener implements a network listener (net.Listener) for TLS connections.
// direct lift from crypto/tls.go
type TLSListener struct {
net.Listener
config *tls.Config
}

// Accept waits for and returns the next incoming TLS connection.
// The returned connection c is a *tls.Conn.
func (l *TLSListener) Accept() (c net.Conn, err error) {
c, err = l.Listener.Accept()
if err != nil {
return
}
c = tls.Server(&gracefulConn{Conn: c}, l.config)
return
}

// NewListener creates a Listener which accepts connections from an inner
// Listener and wraps each connection with Server.
// The configuration config must be non-nil and must have
// at least one certificate.
func NewTLSListener(inner net.Listener, config *tls.Config) net.Listener {
l := new(TLSListener)
l.Listener = inner
l.config = config
return l
}

type listenerAlreadyClosed struct {
error
}

// TCPKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
//
// direct lift from net/http/server.go
type TCPKeepAliveListener struct {
*net.TCPListener
}

func (ln TCPKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}

func getListenerFile(listener net.Listener) (*os.File, error) {
switch t := listener.(type) {
case *net.TCPListener:
return t.File()
case *net.UnixListener:
return t.File()
case TCPKeepAliveListener:
return t.TCPListener.File()
case *TLSListener:
return getListenerFile(t.Listener)
}
return nil, fmt.Errorf("Unsupported listener: %T", listener)
}
17 changes: 17 additions & 0 deletions logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package manners

import (
"io/ioutil"
"log"
)

var logger = log.New(ioutil.Discard, "", 0)

// SetLogger changes the logger used for the startup and shutdown messages
// generated by Manners. By default, no log messages are emitted.
// To make Manners logging behave the same as per the standard
// log package, i.e. to stderr, use
// `SetLogger(log.New(os.Stderr, "", log.LstdFlags))`
func SetLogger(l *log.Logger) {
logger = l
}
Loading