~mna/kick

kick/kick.go -rw-r--r-- 14.3 KiB View raw
e3f133a2Martin Angers get stack before call 9 months ago
                                                                                
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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
package kick

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"sync/atomic"
	"time"

	"github.com/unrolled/secure"
)

// ErrTooManyBytes is the error returned by writes to
// http.ResponseWriter when a handler configured with
// MaxResponseBodyBytes tries to write too many bytes.
var ErrTooManyBytes = errors.New("too many bytes")

// A Server defines parameters for running an HTTP server.
// Its fields must be set prior to calling Build or
// ListenAndServe, and should not be modified afterwards.
// Once closed, a Server cannot be restarted.
type Server struct {
	// Builders identifies the functions to use to build the Server.
	// It must be explicitly set, so that the built-in builder
	// package is only imported if explicitly desired (as it also
	// imports the full list of dependencies).
	Builders *Builders

	// Addr is the TCP address to listen on, <addr>:<port>.
	Addr string

	// Timeouts are transferred as-is to the corresponding
	// http.Server fields, see that type's fields documentation
	// for details.
	ReadTimeout       time.Duration
	ReadHeaderTimeout time.Duration
	WriteTimeout      time.Duration
	IdleTimeout       time.Duration

	// MaxHeaderBytes is transferred as-is to the corresponding
	// http.Server field, see that field's documentation for
	// details.
	MaxHeaderBytes int

	// Root defines the handler options that act on the
	// server as a whole, before applying routing logic defined in
	// Routes.
	Root *Root

	// Routes is the list of routes served by the server.
	Routes []*Route

	// TLS configures the TLS settings of the server.
	TLS *TLSConfig

	// GracefulShutdown enables the graceful shutdown of the
	// server on configured signals, or when the context passed
	// to ListenAndServe is canceled. If nil, there is no automatic
	// graceful shutdown.
	GracefulShutdown *GracefulShutdownConfig

	// ConnStateHook and ServerStateHook are optional function
	// callbacks that get called when the connection or the
	// server (respectively) transition from one state to the
	// next. ConnStateHook is transferred as-is to the
	// http.Server.ConnState field. See the ServerState type
	// and associated constants for details about the
	// ServerStateHook.
	ConnStateHook   func(net.Conn, http.ConnState)
	ServerStateHook func(*http.Server, ServerState)

	// ErrorLog is the logger used to log errors that may occur
	// while serving connections. It uses the standard library's
	// logger as this is what is supported by the http.Server,
	// but many popular logging packages provide adapters for
	// that logger.
	ErrorLog *log.Logger

	// generated by Build
	srv   *http.Server
	built bool
	// handled by s.transit, atomically
	state int32
}

// Build generates the properly configured http.Server and
// net.Listener from the Server struct. Everything is ready
// to run by calling Serve on the returned http.Server and
// passing it the returned net.Listener.
func (s *Server) Build() error {
	if s.built {
		return errors.New("server already built")
	}
	return s.build()
}

func (s *Server) build() error {
	s.built = true

	if s.Builders == nil {
		return errors.New("Server.Builders not set")
	}

	tc, err := s.Builders.TLS(s)
	if err != nil {
		return err
	}

	srv, err := s.Builders.HTTPServer(s)
	if err != nil {
		return err
	}

	if s.TLS != nil && s.TLS.DisableHTTP2 && srv.TLSNextProto == nil {
		srv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
	}

	h, err := s.Builders.Handler(s)
	if err != nil {
		return err
	}

	srv.TLSConfig = tc
	srv.Handler = h
	s.srv = srv

	return nil
}

// HTTPServer returns the generated http.Server. It calls
// Build if it hasn't been called yet.
func (s *Server) HTTPServer() (*http.Server, error) {
	if !s.built {
		if err := s.build(); err != nil {
			return nil, err
		}
	}
	return s.srv, nil
}

// ListenAndServe calls Build if the Server hasn't been
// generated yet, and then starts listening and serving
// connections. It returns the error returned from
// http.Server.ListenAndServe[TLS], which is always non-nil but is
// http.ErrServerClosed if the server was closed explicitly.
//
// If GracefulShutdown is configured, the server is shutdown
// gracefully when the ctx is cancelled or the configured
// signals are caught, otherwise the server is closed when
// the ctx is cancelled.
func (s *Server) ListenAndServe(ctx context.Context) error {
	if !s.built {
		if err := s.build(); err != nil {
			return err
		}
	}
	if atomic.LoadInt32(&s.state) == int32(StateClosed) {
		return http.ErrServerClosed
	}

	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	conf := s.GracefulShutdown
	if conf != nil {
		if len(conf.Signals) > 0 {
			// listen for signals to trigger graceful shutdown
			ch := make(chan os.Signal, 1)
			signal.Notify(ch, conf.Signals...)
			go func() {
				<-ch
				cancel()
			}()

			// unblock the goroutine on exit if cancelled by other means
			defer func() {
				signal.Stop(ch)
				close(ch)
			}()
		}
	}

	srv := s.srv

	s.transit(StateNew)
	defer s.transit(StateClosed)

	var lasErr error
	ch := make(chan struct{})
	go func() {
		defer close(ch)

		s.transit(StateListening)
		if srv.TLSConfig != nil {
			lasErr = srv.ListenAndServeTLS("", "")
			return
		}
		lasErr = srv.ListenAndServe()
	}()

	select {
	case <-ctx.Done():
		// initiate the graceful shutdown or explicit close request
		var err error

		if conf != nil {
			// transition before calling shutdown, as shutdown hooks are called
			// in goroutines and may end up calling the server hooks *after*
			// StateClosed has been sent.
			s.transit(StateShutdown)
			sdctx, cancel := context.WithTimeout(context.Background(), conf.Timeout)
			defer cancel()
			err = srv.Shutdown(sdctx)
		} else {
			err = srv.Close()
		}
		<-ch
		return err

	case <-ch:
		// if srv.ListenAndServe[TLS] returned by itself, then it failed
		// for some reason, return that error.
		return lasErr
	}
}

func (s *Server) transit(to ServerState) {
	atomic.StoreInt32(&s.state, int32(to))
	if s.ServerStateHook != nil {
		s.ServerStateHook(s.srv, to)
	}
}

// ServerState defines the various states the Server may be in.
type ServerState int32

const (
	// StateNew represents a new Server that is expected to start
	// listening and serving immediately. Servers start at this
	// state and transition to StateListening or StateClosed.
	StateNew ServerState = iota

	// StateListening represents a Server that will start listening
	// and serving connections. From that state, it may transition
	// to StateShutdown or StateClosed.
	StateListening

	// StateShutdown represents a Server that started a graceful
	// shutdown. It may only transition to StateClosed.
	StateShutdown

	// StateClosed represents a closed Server. This is a terminal
	// state.
	StateClosed
)

var stringServerState = [...]string{
	StateNew:       "StateNew",
	StateListening: "StateListening",
	StateShutdown:  "StateShutdown",
	StateClosed:    "StateClosed",
}

func (s ServerState) String() string {
	if s < 0 || int(s) >= len(stringServerState) {
		return fmt.Sprintf("<unknown:%d>", s)
	}
	return stringServerState[s]
}

// Builders holds the functions to call to build the Server.
// It is deliberately not very granular, as the goal of the package
// is not flexibility or pluggability, but an opinionated approach
// based on high-quality, battle-proven and simple packages.
type Builders struct {
	TLS        func(*Server) (*tls.Config, error)
	HTTPServer func(*Server) (*http.Server, error)
	Handler    func(*Server) (http.Handler, error)

	_ struct{} // prevent unkeyed struct creation
}

// TLSMode is the type of the predefined TLS configuration modes.
type TLSMode int

// List of supported TLS modes.
const (
	TLSDefault TLSMode = iota
	TLSIntermediate
	TLSModern
)

var stringTLSMode = [...]string{
	TLSDefault:      "TLSDefault",
	TLSIntermediate: "TLSIntermediate",
	TLSModern:       "TLSModern",
}

func (m TLSMode) String() string {
	if m < 0 || int(m) >= len(stringTLSMode) {
		return fmt.Sprintf("<unknown:%d>", m)
	}
	return stringTLSMode[m]
}

// TLSConfig configures the TLS settings of the server.
// See https://wiki.mozilla.org/Security/Server_Side_TLS and
// https://blog.cloudflare.com/exposing-go-on-the-internet/
// for more details.
type TLSConfig struct {
	// Mode is the TLS mode to use. It is just a hint passed to the
	// TLS builder, and is a moving target as cryptography evolves
	// and so do configuration recommendations.
	Mode TLSMode

	// AutoCert indicates if the TLS certificate should be automatically
	// generated and renewed.
	AutoCert bool

	// AutoCertCacheDir is the directory path where auto-generated
	// certificates are stored. This should be a secure directory,
	// not exposed to the internet, but the process running the server
	// must have write access to it. If the directory does not exist,
	// it will be created with 0700 permissions.
	AutoCertCacheDir string

	// AutoCertExactHosts is the list of host names that will be allowed
	// by the generated certificate. Only exact matches are supported,
	// no wildcards.
	AutoCertExactHosts []string

	// AutoCertContactEmail is an optional email address that CAs should use
	// to notify about issues with certificates.
	AutoCertContactEmail string

	// If AutoCert is false, CertFile and KeyFile are used as
	// certificate files.
	CertFile string
	KeyFile  string

	// DisableHTTP2 indicates that the TLS server should not support
	// HTTP/2. By default, a Server with a TLSConfig would automatically
	// support HTTP/2, but it may be useful in certain conditions to
	// disable that automatic support (e.g. for Websockets, at least
	// until those are supported on HTTP/2).
	//
	// This is an option on TLSConfig and not directly on the Server,
	// because automatic HTTP/2 support is only enabled for TLS servers.
	DisableHTTP2 bool

	_ struct{} // prevent unkeyed struct creation
}

// GracefulShutdownConfig configures the graceful shutdown
// of the server.
type GracefulShutdownConfig struct {
	// Timeout indicates the time allowed for graceful shutdown
	// to proceed.
	Timeout time.Duration

	// Signals is the list of OS signals that initiate a
	// graceful shutdown when caught.
	Signals []os.Signal

	_ struct{} // prevent unkeyed struct creation
}

// Root defines handler options that apply to the
// server as a whole, before multiplexing the request based on
// method and path as defined by the Routes field of the server.
type Root struct {
	// PanicRecoveryFunc is the function called to recover from
	// a panic. It should write the response, but the panic is
	// already recovered when this is called, and the recovered
	// value is passed as panicVal. If nil, there is no panic
	// recovery.
	PanicRecoveryFunc func(w http.ResponseWriter, r *http.Request, panicVal interface{}, stackTrace []byte)

	// LoggingFunc is the function called to log a request.
	// Relevant information is stored in the info map.
	LoggingFunc func(w http.ResponseWriter, r *http.Request, info map[string]interface{})

	// MethodNotAllowedHandler is the handler called if a request
	// is made to a path that has a configured handler, but no
	// handler for that specific method.
	MethodNotAllowedHandler http.Handler

	// NotFoundHandler is the handler called if no match is found
	// for the requested method and path.
	NotFoundHandler http.Handler

	// RouterMiddleware is a middleware function that is applied to
	// the router itself, meaning that it's handler will run for all
	// routes, including the NotFound and MethodNotAllowed handlers,
	// but ensuring it runs after the root middleware so that e.g.
	// method overrides, request IDs, etc. are applied first.
	RouterMiddleware func(http.Handler) http.Handler

	TrustProxyHeaders   bool
	AllowMethodOverride bool

	CanonicalHost                   string
	CanonicalHostRedirectStatusCode int

	RequestIDHeader   string
	RequestIDForceNew bool

	// SecurityHeaders configures subtle but important HTTP headers
	// for better secufiry. This is currently a direct usage of the
	// unrolled/secure package's Options struct, should probably be
	// abstracted to a package-local struct and that 3rd-party package
	// used only in the builder.
	//
	// See https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Headers
	// for an overview of the headers and their role.
	SecurityHeaders *secure.Options

	Gzip *GzipConfig
}

// A Route defines a single route and handler to call if all
// conditions match.
type Route struct {
	// Method matches the route if it has the specified method.
	Method string

	// Path matches the route if it has the specified path.
	Path string

	// Handler is the handler to call when this route matches.
	Handler http.Handler

	// Config configures this handler.
	Config *HandlerConfig

	_ struct{} // prevent unkeyed struct creation
}

// HandlerConfig configures a handler. The same HandlerConfig
// may be used on multiple handlers that need to be configured
// the same way.
type HandlerConfig struct {
	HandlerTimeout       time.Duration
	MaxRequestBodyBytes  int64
	MaxResponseBodyBytes int64

	RequestContentTypes  []string
	ResponseContentTypes []string

	CORS         *CORSConfig
	CSRF         *CSRFConfig
	RequestLimit *RequestLimitConfig

	_ struct{} // prevent unkeyed creation of this struct
}

// CORSConfig configures the cross-origin resource sharing.
type CORSConfig struct {
	AllowCredentials bool
	AllowedHeaders   []string
	AllowedMethods   []string
	AllowedOrigins   []string
	ExposedHeaders   []string
	MaxAge           time.Duration

	OptionsStatusCode int

	_ struct{} // prevent unkeyed creation of this struct
}

// GzipConfig configures compression of response bodies.
type GzipConfig struct {
	CompressionLevel int
	ContentTypes     []string
	MinSize          int

	_ struct{} // prevent unkeyed creation of this struct
}

// CSRFConfig configures the cross-site request forgery protection
// via a token.
type CSRFConfig struct {
	AuthKey       []byte
	CookieName    string
	Domain        string
	FormFieldName string
	RequestHeader string
	HTTPOnly      bool
	MaxAge        time.Duration
	Path          string
	Secure        bool

	_ struct{} // prevent unkeyed creation of this struct
}

// RequestLimitConfig configures the rate of requests allowed
// for an endpoint. It returns a status code of 503 Service
// Unavailable when requests exceed the configured capacity.
type RequestLimitConfig struct {
	Capacity     int64
	FillInterval time.Duration
	Quantum      int64
	Rate         float64
	MaxWait      time.Duration

	_ struct{} // prevent unkeyed creation of this struct
}