This repository has been archived by the owner on Apr 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.go
147 lines (113 loc) · 3.11 KB
/
main.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
package main
import (
"context"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/NYTimes/gziphandler"
"github.com/sirupsen/logrus"
"github.com/stevenroose/gonfig"
)
var version = "devel"
var logger *logrus.Logger
func main() {
logger = logrus.New()
if err := gonfig.Load(&conf, gonfig.Conf{
EnvPrefix: "ATTO_",
}); err != nil {
logger.Fatalf("could not load config: %s", err)
}
if level, err := logrus.ParseLevel(conf.LogLevel); err == nil {
logger.SetLevel(level)
} else {
logger.Fatal(err)
}
log.SetOutput(logger.Writer())
logger.WithFields(logrus.Fields{
"version": version,
}).Info("starting atto")
handler := http.StripPrefix(conf.Prefix, http.FileServer(safeDir(conf.Path)))
if conf.Compress {
handler = gziphandler.GzipHandler(handler)
}
if conf.Canonical.Host != "" {
handler = canonicalRedirectMiddleware(handler.ServeHTTP)
}
server := http.Server{
Addr: fmt.Sprintf(":%d", conf.Port),
Handler: loggingMiddleware(handler.ServeHTTP),
ReadHeaderTimeout: time.Duration(*conf.Timeout.ReadHeader),
ReadTimeout: time.Duration(*conf.Timeout.ReadHeader), // we do not expect any content upload, so headers are enough
ErrorLog: log.New(logger.WithField("source", "http.Server").WriterLevel(logrus.WarnLevel), "", 0),
}
server.RegisterOnShutdown(func() {
logger.WithFields(logrus.Fields{
"timeout": time.Duration(*conf.Timeout.Shutdown),
}).Info("shutting down gracefully")
})
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
logger.WithFields(logrus.Fields{
"port": conf.Port,
}).Debug("starting server")
if err := server.ListenAndServe(); err != http.ErrServerClosed {
logger.Fatal(err)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
handleSignals(&server)
}()
wg.Wait()
}
func loggingMiddleware(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
logger.WithFields(logrus.Fields{
"host": r.Host,
"path": r.URL.Path,
"method": r.Method,
}).Info("handled request")
}
}
func canonicalRedirectMiddleware(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
host := r.Host
if hostNoPort, _, err := net.SplitHostPort(r.Host); err == nil {
host = hostNoPort
}
if conf.Canonical.Host != host {
logger.WithFields(logrus.Fields{
"canonical": conf.Canonical.Host,
}).Debug("redirecting to canonical host")
u := r.URL
u.Host = conf.Canonical.Host
http.Redirect(w, r, u.String(), conf.Canonical.StatusCode)
return
}
h.ServeHTTP(w, r)
}
}
func handleSignals(server *http.Server) {
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
<-signalChan
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(*conf.Timeout.Shutdown))
defer cancel()
if err := server.Shutdown(ctx); err != nil {
if err == context.DeadlineExceeded {
logger.Warn("timeout exceeded while shutting down")
} else {
logger.WithError(err).Error("error while shutting down server")
}
}
}