-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.go
138 lines (130 loc) · 4.91 KB
/
config.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
package webserv
import (
"context"
"io/fs"
"net"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
type Config struct {
Address string // optional specific address (and/or port) to listen on
CertDir string // if set, directory to look for fullchain.pem and privkey.pem
FullchainPem string // set to override filename for "fullchain.pem"
PrivkeyPem string // set to override filename for "privkey.pem"
User string // if set, user to switch to after opening listening port
DataDir string // if set, create this directory, if unset will be filled in after Listen
DefaultDataDirSuffix string // if set and DataDir is not set, set DataDir to the user's default data dir plus this suffix
DataDirMode fs.FileMode // if nonzero, create DataDir if it does not exist using this mode
ListenURL string // if set, the external URL clients can reach us at, if unset will be filled in after Listen (e.g. "https://localhost:8443")
Logger Logger // logger to use, if nil logs nothing
mu sync.Mutex // protects following
breakChan chan os.Signal // break channel
}
// BreakChan returns the break signalling channel, or nil if not yet started serving requests.
func (cfg *Config) BreakChan() (ch chan<- os.Signal) {
cfg.mu.Lock()
ch = cfg.breakChan
cfg.mu.Unlock()
return
}
func (cfg *Config) logInfo(msg string, keyValuePairs ...any) {
if cfg.Logger != nil && len(keyValuePairs) > 1 {
s, ok := keyValuePairs[1].(string)
if !(ok && s == "") {
cfg.Logger.Info("webserv: "+msg, keyValuePairs...)
}
}
}
// Listen performs initial setup for a simple web server and returns a
// net.Listener if successful.
//
// First it loads certificates if cfg.CertDir is set, and then starts a net.Listener
// (TLS or normal). The listener will default to all addresses and standard port
// depending on privileges and if a certificate was loaded or not.
//
// If cfg.Address was set, any address or port given there overrides these defaults.
//
// If cfg.User is set it then switches to that user and the users primary group.
// Note that this is not supported on Windows.
//
// If cfg.DataDir or cfg.DefaultDataDirSuffix is set, calculates the absolute
// data directory path and sets cfg.DataDir. If cfg.DataDirMode is nonzero, the
// directory will be created if necessary.
//
// On a non-error return, cfg.CertDir and cfg.DataDir will be absolute paths or be empty,
// and if cfg.ListenURL was empty it will be set to a best-guess printable and connectable
// URL like "http://localhost:80".
func (cfg *Config) Listen() (l net.Listener, err error) {
if l, cfg.ListenURL, cfg.CertDir, err = Listener(cfg.Address, cfg.CertDir, cfg.FullchainPem, cfg.PrivkeyPem, cfg.ListenURL); err == nil {
cfg.logInfo("loaded certificates", "dir", cfg.CertDir)
if err = BecomeUser(cfg.User); err == nil {
cfg.logInfo("user switched", "user", cfg.User)
if cfg.DataDir, err = DefaultDataDir(cfg.DataDir, cfg.DefaultDataDirSuffix); err == nil {
if cfg.DataDir, err = UseDataDir(cfg.DataDir, cfg.DataDirMode); err == nil {
cfg.logInfo("data directory", "dir", cfg.DataDir)
}
}
}
}
return
}
// ServeWith sets up a signal handler to catch SIGINT and SIGTERM and then calls srv.Serve(l).
//
// If the context is cancelled or a signal is received, calls srv.Shutdown(ctx).
// Returns nil if the server started successfully and then cleanly shut down.
func (cfg *Config) ServeWith(ctx context.Context, srv *http.Server, l net.Listener) error {
breakChan := make(chan os.Signal, 1)
signal.Notify(breakChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
cfg.logInfo("listening on", "address", l.Addr(), "url", cfg.ListenURL)
go func() {
cfg.mu.Lock()
cfg.breakChan = breakChan
cfg.mu.Unlock()
select {
case sig, ok := <-breakChan:
if ok {
defer close(breakChan)
cfg.logInfo("received signal", "sig", sig.String())
}
case <-ctx.Done():
defer close(breakChan)
cfg.logInfo("context done", "err", context.Cause(ctx))
}
signal.Stop(breakChan)
_ = srv.Shutdown(ctx)
}()
err := srv.Serve(l)
for err == http.ErrServerClosed {
select {
case _, ok := <-breakChan:
if !ok {
err = nil
}
case <-ctx.Done():
return context.Cause(ctx)
}
}
return err
}
// Serve creates a http.Server with reasonable defaults and calls ServeWith.
func (cfg *Config) Serve(ctx context.Context, l net.Listener, handler http.Handler) error {
srv := &http.Server{
Handler: handler,
ReadHeaderTimeout: time.Second * 5,
}
return cfg.ServeWith(ctx, srv, l)
}
// ListenAndServe calls Listen followed by Serve.
func (cfg *Config) ListenAndServe(ctx context.Context, handler http.Handler) (err error) {
if err = ctx.Err(); err == nil {
var l net.Listener
if l, err = cfg.Listen(); err == nil {
err = cfg.Serve(ctx, l, handler)
}
}
return
}