commit 19bcef65fa57db6ba5e24cf4ff46d67822a659a9 Author: ston1th Date: Tue Sep 6 19:55:16 2016 +0200 initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9e52d9b --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (C) 2016 Marius Schellenberger +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * The names of the authors and/or contributors may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ston1th BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dcb600d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# graceful - a simple library for graceful tcp connection handling in Go diff --git a/graceful.go b/graceful.go new file mode 100644 index 0000000..8518df2 --- /dev/null +++ b/graceful.go @@ -0,0 +1,53 @@ +// Package graceful provides a graceful tcp handling library +package graceful + +import ( + "errors" + "net" + "time" +) + +const network = "tcp" + +var ErrInterfaceConversion = errors.New("interface conversion failed") + +// Listener is a wrapper for net.TCPListener +type Listener struct { + *net.TCPListener + + keepAlive time.Duration +} + +// New wraps Listener around net.TCPListener +func New(laddr string, keepAlive time.Duration) (*Listener, error) { + l, err := net.Listen(network, laddr) + if err != nil { + return nil, err + } + tcpl, ok := l.(*net.TCPListener) + if !ok { + return nil, ErrInterfaceConversion + } + return &Listener{ + TCPListener: tcpl, + keepAlive: keepAlive, + }, nil +} + +// Accept wraps net.TCPListener.AcceptTCP method +func (l *Listener) Accept() (net.Conn, error) { + conn, err := l.AcceptTCP() + if err != nil { + return conn, err + } + if l.keepAlive > 0 { + conn.SetKeepAlive(true) + conn.SetKeepAlivePeriod(l.keepAlive) + } + return conn, nil +} + +// Stop stops the listener +func (l *Listener) Stop() { + l.TCPListener.Close() +} diff --git a/tls.go b/tls.go new file mode 100644 index 0000000..f99d045 --- /dev/null +++ b/tls.go @@ -0,0 +1,94 @@ +package graceful + +import ( + "crypto/tls" + "errors" + "net" + "net/http" + "sync" + "time" +) + +// TLSListener is a TLS wrapper for net.TCPListener +type TLSListener struct { + // protects conns + sync.Mutex + + *net.TCPListener + + config *tls.Config + conns map[net.Conn]struct{} + keepAlive time.Duration +} + +// TLSConn is a net.Conn wrapper +type TLSConn struct { + net.Conn + *TLSListener +} + +// Close wraps the net.Conn.Close method and removes the conn from the listener map +func (c *TLSConn) Close() error { + c.RemoveConn(c.Conn) + return c.Conn.Close() +} + +// NewTLS wraps TLSListener around net.TCPListener +func NewTLS(laddr string, config *tls.Config, keepAlive time.Duration) (*TLSListener, error) { + if config == nil || len(config.Certificates) == 0 { + return nil, errors.New("NewTLS: no certificates in configuration") + } + l, err := net.Listen(network, laddr) + if err != nil { + return nil, err + } + ltcp, ok := l.(*net.TCPListener) + if !ok { + return nil, ErrInterfaceConversion + } + return &TLSListener{ + TCPListener: ltcp, + config: config, + conns: make(map[net.Conn]struct{}), + keepAlive: keepAlive, + }, nil +} + +// Accept wraps the net.TCPListener.AcceptTCP method +func (l *TLSListener) Accept() (net.Conn, error) { + conn, err := l.AcceptTCP() + if err != nil { + return conn, err + } + if l.keepAlive > 0 { + conn.SetKeepAlive(true) + conn.SetKeepAlivePeriod(l.keepAlive) + } + tlsconn := &TLSConn{tls.Server(conn, l.config), l} + l.Lock() + defer l.Unlock() + l.conns[tlsconn] = struct{}{} + return tlsconn, nil +} + +// TLSConnState implements HTTP connection hijacking +func (l *TLSListener) TLSConnState(c net.Conn, cs http.ConnState) { + if cs == http.StateClosed || cs == http.StateHijacked { + l.RemoveConn(c) + } +} + +// RemoveConn removes a connection from the map +func (l *TLSListener) RemoveConn(c net.Conn) { + l.Lock() + defer l.Unlock() + delete(l.conns, c) +} + +// Stop stops the listener and closes all open connections +func (l *TLSListener) Stop() { + l.Close() + for c := range l.conns { + c.Close() + } +}