-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathapp.go
176 lines (147 loc) · 3.94 KB
/
app.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
package otto
import (
gocontext "context"
"crypto/tls"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/pkg/errors"
"golang.org/x/crypto/acme/autocert"
)
// Options has all options that can be passed to App
type Options struct {
Addr string
StrictSlash bool
ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxHeaderBytes int
ctx gocontext.Context
cancel gocontext.CancelFunc
DisableHTTP2 bool
}
// NewOptions creates new Options with default values
func NewOptions() Options {
return defaultOptions(Options{})
}
// App holds on to options and the underlying router, the middleware, and more
type App struct {
*Router
opts Options
autoTLSManager autocert.Manager
tlsConfig *tls.Config
certFile string
keyFile string
}
// New creates a new App
func New(opts Options) *App {
return &App{
Router: NewRouter(opts.StrictSlash),
opts: opts,
autoTLSManager: autocert.Manager{
Prompt: autocert.AcceptTOS,
},
}
}
// UseAutoTLS will setup autocert.Manager and request cert from https://letsencrypt.org
func (a *App) UseAutoTLS(cache autocert.DirCache) {
a.autoTLSManager.Cache = cache
// start autotlsmanager httphandler
go http.ListenAndServe(":http", a.autoTLSManager.HTTPHandler(nil))
a.tlsConfig = new(tls.Config)
a.tlsConfig.GetCertificate = a.autoTLSManager.GetCertificate
}
// UseTLS will use tls.LoadX509KeyPair to setup certificate for TLS
func (a *App) UseTLS(certFile, keyFile string) error {
if certFile == "" || keyFile == "" {
return errors.New("invalid tls configuration")
}
a.tlsConfig = new(tls.Config)
a.tlsConfig.Certificates = make([]tls.Certificate, 1)
var err error
if a.tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile); err != nil {
return errors.Wrapf(err, "could not load keypair from %s %s", certFile, keyFile)
}
return nil
}
// Serve serves the application at the specified address and
// listening to OS interrupt and kill signals and will try to shutdown the
// app gracefully
func (a *App) Serve() error {
ctx, cancel := interruptWithCancel(a.opts.ctx)
defer cancel()
var s *http.Server
s = &http.Server{
Addr: a.opts.Addr,
Handler: a,
ReadHeaderTimeout: a.opts.ReadHeaderTimeout,
WriteTimeout: a.opts.WriteTimeout,
IdleTimeout: a.opts.IdleTimeout,
MaxHeaderBytes: a.opts.MaxHeaderBytes,
TLSConfig: a.tlsConfig,
}
var err error
go func() {
if a.tlsConfig == nil {
err = a.serve(s)
} else {
err = a.serveTLS(s)
}
}()
if err != nil {
return err
}
<-ctx.Done()
return a.Close(s.Shutdown(ctx))
}
// Close the application and try to shutdown gracefully
func (a *App) Close(err error) error {
a.opts.cancel()
if err != gocontext.Canceled {
return errors.WithStack(err)
}
return nil
}
func (a *App) serve(s *http.Server) error {
if err := s.ListenAndServe(); err != nil {
return a.Close(err)
}
return nil
}
func (a *App) serveTLS(s *http.Server) error {
if !a.opts.DisableHTTP2 {
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
}
if err := s.ListenAndServeTLS(a.certFile, a.keyFile); err != nil {
return a.Close(err)
}
return nil
}
func interruptWithCancel(parentContext gocontext.Context) (gocontext.Context, gocontext.CancelFunc) {
ctx, cancel := gocontext.WithCancel(parentContext)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
select {
case <-c:
cancel()
case <-parentContext.Done():
cancel()
}
signal.Stop(c)
}()
return ctx, cancel
}
func defaultOptions(opts Options) Options {
ctx, cancel := gocontext.WithCancel(gocontext.Background())
opts.ReadHeaderTimeout = 1 * time.Second
opts.WriteTimeout = 10 * time.Second
opts.IdleTimeout = 90 * time.Second
opts.MaxHeaderBytes = http.DefaultMaxHeaderBytes
opts.StrictSlash = false
opts.cancel = cancel
opts.ctx = ctx
return opts
}