/
server.go
144 lines (112 loc) · 2.86 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package srvutil
import (
"context"
"net"
"net/http"
"time"
"github.com/gorilla/mux"
"gopkg.in/tomb.v2"
"github.com/Shopify/goose/logger"
"github.com/Shopify/goose/safely"
)
const (
keepAlivePeriod = 3 * time.Minute
)
// Server wraps an http.Server to make it runnable and stoppable
// If its tomb dies, the server will be stopped
type Server interface {
safely.Runnable
Addr() *net.TCPAddr
}
func NewServer(t *tomb.Tomb, bind string, servlet Servlet) Server {
return NewServerFromFactory(t, servlet, func(handler http.Handler) http.Server {
return http.Server{ //nolint:gosec
Addr: bind,
Handler: handler,
}
})
}
type ServerFactory func(handler http.Handler) http.Server
func NewServerFromFactory(t *tomb.Tomb, servlet Servlet, factory ServerFactory) Server {
router := mux.NewRouter()
servlet.RegisterRouting(router)
return &server{
server: factory(router),
haveAddr: make(chan struct{}),
tomb: t,
}
}
type server struct {
server http.Server
tomb *tomb.Tomb
haveAddr chan struct{}
addr *net.TCPAddr
}
func (c *server) Tomb() *tomb.Tomb {
return c.tomb
}
func (c *server) Addr() *net.TCPAddr {
<-c.haveAddr
return c.addr
}
func (c *server) Run() error {
ctx := logger.WithField(context.Background(), "bind", c.server.Addr)
log(ctx, nil).Info("starting server")
ln, err := net.Listen("tcp", c.server.Addr)
if err != nil {
return err
}
c.addr = ln.Addr().(*net.TCPAddr)
close(c.haveAddr)
ctx = logger.WithField(ctx, "addr", c.addr.String())
log(ctx, nil).Info("started server")
defer log(ctx, nil).Debug("stopped server")
listener := stoppableKeepaliveListener{
TCPListener: ln.(*net.TCPListener),
tomb: c.tomb,
}
shutdown := make(chan error)
go func() {
<-c.tomb.Dying()
log(ctx, c.tomb.Err()).Info("shutting down server")
// Call Shutdown to allow in-flight requests to gracefully complete.
ctx := context.Background()
shutdown <- c.server.Shutdown(ctx)
}()
if err := c.server.Serve(listener); err != http.ErrServerClosed {
return err
}
log(ctx, nil).Debug("waiting for server to complete shutdown")
return <-shutdown
}
type stoppableKeepaliveListener struct {
*net.TCPListener
tomb *tomb.Tomb
}
func (ln stoppableKeepaliveListener) Accept() (net.Conn, error) {
for {
if !ln.tomb.Alive() {
return nil, http.ErrServerClosed
}
if err := ln.SetDeadline(time.Now().Add(500 * time.Millisecond)); err != nil {
return nil, err
}
tc, err := ln.AcceptTCP()
if err != nil {
netErr, ok := err.(net.Error)
// If this is a timeout, then continue to wait for new connections
if ok && netErr.Timeout() && netErr.Temporary() { //nolint:staticcheck
continue
} else {
return nil, err
}
}
if err := tc.SetKeepAlive(true); err != nil {
return nil, err
}
if err := tc.SetKeepAlivePeriod(keepAlivePeriod); err != nil {
return nil, err
}
return tc, nil
}
}