-
Notifications
You must be signed in to change notification settings - Fork 4
/
server.go
237 lines (210 loc) · 7.01 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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package sslmgr
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"golang.org/x/crypto/acme/autocert"
)
// SecureServer is a server which abstracts away acme/autocert's
// certificate manager and server configuration
type SecureServer struct {
server *http.Server
certMgr *autocert.Manager
serveSSLFunc func() bool
httpsPort string
httpPort string
gracefulnessTimeout time.Duration
gracefulShutdownErrHandler func(error)
testing bool
}
// ServerConfig holds configuration to initialize a SecureServer.
// Requied Fields: Hostnames and Handler
// Note that it is strongly recommended not to use the default CertCache
type ServerConfig struct {
// Hostnames for which the server is allowed to serve HTTPS.
// If the server receives an https request through a DNS name or IP
// not contained in this list, the request will be denied
// (REQUIRED)
Hostnames []string
// The server's http handler
// (REQUIRED)
Handler http.Handler
// ServeSSLFunc is called to determine whether to serve HTTPS
// or not. This function's enables users to purpusely disable
// HTTPS i.e. for local development.
// Default behavior is to serve HTTPS
ServeSSLFunc func() bool
// An implementation of the autocert.Cache interface, which autocert
// will use to store and manage certificates. It is strongly recommended
// to provide this field.
// Default behavior is to store at "." in the file system
CertCache autocert.Cache
// Default value is ":443"
HTTPSPort string
// Default value is ":80"
HTTPPort string
// Default value is 5 seconds
ReadTimeout time.Duration
// Default value is 5 seconds
WriteTimeout time.Duration
// Default value is 25 seconds
IdleTimeout time.Duration
// Default value is 5 seconds
GracefulnessTimeout time.Duration
// GracefulShutdownErrHandler is called to handle the event of an error during
// a graceful shutdown (accept no more connections, and wait for existing
// ones to finish within the GracefulnessTimeout)
// Default value is a NOP
GracefulShutdownErrHandler func(error)
}
var (
// ErrNoHostname is returned whenever a user calls NewSecureServer
// without any hostnames in the config
ErrNoHostname = errors.New("no hostnames provided")
// ErrNoHandler is returned whenever a user calls NewSecureServer
// with a nil http.Handler in the config
ErrNoHandler = errors.New("server handler cannot be nil")
// ErrNotAnInteger is returned whenever a user calls NewSecureServer with
// port definitions which do not correspont to integers. i.e. "not a number"
ErrNotAnInteger = errors.New("port number must be a numerical string")
)
// NewSecureServer returns a SecureServer with default configuration
func NewSecureServer(h http.Handler, hostnames ...string) (*SecureServer, error) {
return NewServer(ServerConfig{
Handler: h,
Hostnames: hostnames,
})
}
// NewServer returns a SecureServer with the given config applied
func NewServer(c ServerConfig) (*SecureServer, error) {
// check required fields
if c.Hostnames == nil || len(c.Hostnames) < 1 {
return nil, ErrNoHostname
}
if c.Handler == nil {
return nil, ErrNoHandler
}
// cache implementation cant be empty
if c.CertCache == nil {
c.CertCache = autocert.DirCache(".")
}
// serve SSL by default
if c.ServeSSLFunc == nil {
c.ServeSSLFunc = func() bool {
return true
}
}
// NOP if graceful shutdown fails
if c.GracefulShutdownErrHandler == nil {
c.GracefulShutdownErrHandler = func(e error) { /* NOP */ }
}
ss := &SecureServer{
server: &http.Server{Handler: c.Handler},
certMgr: &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(c.Hostnames...),
Cache: c.CertCache,
},
serveSSLFunc: c.ServeSSLFunc,
gracefulShutdownErrHandler: c.GracefulShutdownErrHandler,
}
if err := ss.setPorts(c.HTTPPort, c.HTTPSPort); err != nil {
return nil, err
}
ss.setTimeouts(c.ReadTimeout, c.WriteTimeout, c.IdleTimeout, c.GracefulnessTimeout)
return ss, nil
}
// setPorts sets the http and https ports on the server
// Note: port definitions cannot be empty nor non numerical strings
func (ss *SecureServer) setPorts(httpPort, httpsPort string) error {
if httpsPort == "" {
httpsPort = ":443"
}
if _, err := strconv.Atoi(strings.TrimPrefix(httpsPort, ":")); err != nil {
return ErrNotAnInteger
}
if !strings.HasPrefix(httpsPort, ":") {
httpsPort = fmt.Sprintf(":%s", httpsPort)
}
if httpPort == "" {
httpPort = ":80"
}
if _, err := strconv.Atoi(strings.TrimPrefix(httpPort, ":")); err != nil {
return ErrNotAnInteger
}
if !strings.HasPrefix(httpPort, ":") {
httpPort = fmt.Sprintf(":%s", httpPort)
}
ss.httpPort = httpPort
ss.httpsPort = httpsPort
return nil
}
// setTimeouts sets server operation and shutdown timeouts
func (ss *SecureServer) setTimeouts(read, write, idle, gracefulness time.Duration) {
if read == time.Duration(0) {
read = 5 * time.Second
}
if write == time.Duration(0) {
write = 5 * time.Second
}
if idle == time.Duration(0) {
idle = 25 * time.Second
}
if gracefulness == time.Duration(0) {
gracefulness = 5 * time.Second
}
ss.server.ReadTimeout = read
ss.server.WriteTimeout = write
ss.server.IdleTimeout = idle
ss.gracefulnessTimeout = gracefulness
}
// ListenAndServe starts the secure server
func (ss *SecureServer) ListenAndServe() {
ss.startGracefulStopHandler(ss.gracefulnessTimeout, ss.gracefulShutdownErrHandler)
if ss.serveSSLFunc() {
ss.serveHTTPS()
}
ss.server.Addr = ss.httpPort
log.Printf("[sslmgr] serving http at %s", ss.httpPort)
if err := ss.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("[sslmgr] ListenAndServe() failed with %s", err)
}
}
func (ss *SecureServer) serveHTTPS() {
ss.server.Addr = ss.httpsPort
ss.server.TLSConfig = &tls.Config{GetCertificate: ss.certMgr.GetCertificate}
go func() {
log.Printf("[sslmgr] serving https at %s", ss.httpsPort)
if err := ss.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
log.Fatalf("[sslmgr] ListendAndServeTLS() failed with %s", err)
}
}()
// allow autocert handler Let's Encrypt auth callbacks over HTTP
ss.server.Handler = ss.certMgr.HTTPHandler(ss.server.Handler)
// some time for OS scheduler to start SSL thread (before changing http.Server port)
time.Sleep(time.Millisecond * 50)
}
func (ss *SecureServer) startGracefulStopHandler(timeout time.Duration, errHandler func(error)) {
gracefulStop := make(chan os.Signal)
signal.Notify(gracefulStop, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-gracefulStop
log.Print("[sslmgr] shutdown signal received, draining existing connections...")
ctx, cncl := context.WithTimeout(context.Background(), timeout)
defer cncl()
if err := ss.server.Shutdown(ctx); err != nil {
log.Printf("[sslmgr] server could not be shutdown gracefully: %s", err)
errHandler(err)
}
log.Print("[sslmgr] server was closed successfully with no service interruptions")
}()
}