Skip to content

Commit

Permalink
Add GracefulServer
Browse files Browse the repository at this point in the history
  • Loading branch information
bahlo committed Sep 2, 2015
1 parent 7311681 commit f96e784
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 0 deletions.
79 changes: 79 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package abutil

import (
"net"
"net/http"
"strconv"
"strings"
"sync"
"time"

"github.com/tylerb/graceful"
)

// RemoteIP tries to get the remote ip and returns it or ""
Expand All @@ -24,3 +30,76 @@ func RemoteIP(r *http.Request) string {

return a
}

// GracefulServer is a wrapper around graceful.Server from
// github.com/tylerb/graceful, but adds a running variable and a mutex for
// controlling the access to it.
type GracefulServer struct {
server *graceful.Server

// stopped determines if the server is stopped
stopped bool

// locker controls the access to running
locker sync.Locker
}

// NewGracefulServer creates a new GracefulServer with the given handler,
// which listens on the given port. When Stop() ist called, it waits until
// the timeout is finished or all connections are closed (whatever comes first)
func NewGracefulServer(p int, h http.Handler, t time.Duration) *GracefulServer {
var m sync.Mutex
return &GracefulServer{
server: &graceful.Server{
Server: &http.Server{
Addr: ":" + strconv.Itoa(p),
Handler: h,
},
NoSignalHandling: true,
Timeout: t,
},
stopped: true,
locker: &m,
}
}

// Stopped returns if the server is running
func (g *GracefulServer) Stopped() bool {
g.locker.Lock()
defer g.locker.Unlock()

return g.stopped
}

func (g *GracefulServer) setStopped(r bool) {
g.locker.Lock()
g.stopped = r
g.locker.Unlock()
}

// Stop stops the server (this may last up to the server timeout)
func (g *GracefulServer) Stop() {
g.setStopped(true)

g.server.Stop(g.server.Timeout)
}

// Serve is equivalent to http.Server.Serve, but the server is stoppable
func (g *GracefulServer) Serve(l net.Listener) error {
g.setStopped(false)
return g.server.Serve(l)
}

// ListenAndServe is equivalent to http.Server.Serve, but the server is
// stoppable
func (g *GracefulServer) ListenAndServe() error {
g.setStopped(false)
return g.server.ListenAndServe()
}

// ListenAndServeTLS is equivalent to http.Server.Serve, but the server is
// stoppable
func (g *GracefulServer) ListenAndServeTLS(cf, kf string) error {
g.setStopped(false)
return g.server.ListenAndServeTLS(cf, kf)
}
95 changes: 95 additions & 0 deletions http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"net/http/httptest"
"testing"
"time"
)

func mockRequestContext(t *testing.T, fn func(*http.Request)) {
Expand Down Expand Up @@ -91,3 +92,97 @@ func ExampleRemoteIP() {

// Output: New request from 123.456.7.8
}

func gracefulServerContext(t *testing.T, fn func(*GracefulServer)) {
p := 1337
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Foobar"))
})
to := 1 * time.Second

fn(NewGracefulServer(p, h, to))
}

func TestNewGracefulServer(t *testing.T) {
gracefulServerContext(t, func(s *GracefulServer) {
if s.server.NoSignalHandling != true {
t.Error("NoSignalHandling should be true")
}

if s.server.Addr != ":1337" {
t.Error("Didn't set the port correctly")
}
})
}

func TestGracefulServerStopped(t *testing.T) {
gracefulServerContext(t, func(s *GracefulServer) {
if !s.Stopped() {
t.Error("Stopped returned false, but shouldn't")
}

s.setStopped(false)

if s.Stopped() {
t.Error("Stopped returned true, but shouldn't")
}
})
}

func TestGracefulServerStop(t *testing.T) {
done := make(chan bool)

gracefulServerContext(t, func(s *GracefulServer) {
time.AfterFunc(10*time.Millisecond, func() {
s.Stop()

if !s.Stopped() {
t.Error("Expected stopped to be true")
}

done <- true
})

s.ListenAndServe()
})

<-done
}

func TestGracefulServerListenAndServe(t *testing.T) {
done := make(chan bool)

gracefulServerContext(t, func(s *GracefulServer) {
time.AfterFunc(10*time.Millisecond, func() {
if s.Stopped() {
t.Error("The server should not be stopped after ListenAndServe")
}

s.Stop()
done <- true
})

s.ListenAndServe()
})

<-done
}

func TestGracefulServerListenAndServeTLS(t *testing.T) {
done := make(chan bool)

gracefulServerContext(t, func(s *GracefulServer) {
time.AfterFunc(10*time.Millisecond, func() {
if s.Stopped() {
t.Error("The server should not be stopped after ListenAndServe")
}

s.Stop()
done <- true
})

s.ListenAndServeTLS("foo", "bar")
})

<-done
}

0 comments on commit f96e784

Please sign in to comment.