/
server.go
230 lines (201 loc) · 6.02 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
package main
// server module
//
// Copyright (c) 2022 - Valentin Kuznetsov <vkuznet@gmail.com>
//
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/dmwm/cmsauth"
"github.com/gorilla/mux"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
logging "github.com/vkuznet/http-logging"
)
// global variables
var _top, _bottom, _header, _footer template.HTML
// GitVersion defines git version of the server
var GitVersion string
// ServerInfo defines server info
var ServerInfo string
// StartTime represents initial time when we started the server
var StartTime time.Time
// CMSAuth structure to create CMS Auth headers
var CMSAuth cmsauth.CMSAuth
var wMgr *WMStatsManager
// helper function to provide base path of URL
func basePath(api string) string {
base := Config.Base
if base != "" {
if strings.HasPrefix(api, "/") {
api = strings.Replace(api, "/", "", 1)
}
if strings.HasPrefix(base, "/") {
return fmt.Sprintf("%s/%s", base, api)
}
return fmt.Sprintf("/%s/%s", base, api)
}
return api
}
// Handlers defines all server handlers
func Handlers() *mux.Router {
router := mux.NewRouter()
router.StrictSlash(true) // to allow /route and /route/ end-points
// aux APIs used by server
router.HandleFunc(basePath("/healthz"), StatusHandler).Methods("GET")
router.HandleFunc(basePath("/metrics"), MetricsHandler).Methods("GET")
// main page
router.HandleFunc(basePath("/alerts"), AlertsHandler).Methods("GET")
router.HandleFunc(basePath("/agents"), AgentsHandler).Methods("GET")
router.HandleFunc(basePath("/errorlogs"), ErrorLogsHandler).Methods("GET")
router.HandleFunc(basePath("/workflows"), WorkflowsHandler).Methods("GET")
router.HandleFunc(basePath("/"), MainHandler).Methods("GET")
// for all requests
router.Use(logging.LoggingMiddleware)
// for all requests perform first auth/authz action
router.Use(authMiddleware)
// use limiter middleware to slow down clients
router.Use(limitMiddleware)
return router
}
// helper function to run as go-routine to update WMStats cache
func updateWMStatsCache(wmgr *WMStatsManager, ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
time.Sleep(time.Duration(5) * time.Second)
if wmgr != nil {
wmgr.update()
}
}
}
}
// Server represents main web server for service
//gocyclo:ignore
func Server(configFile string) {
StartTime = time.Now()
err := ParseConfig(configFile)
if err != nil {
log.Fatal(err)
}
log.SetFlags(0)
if Config.Verbose > 0 {
log.SetFlags(log.Lshortfile)
}
log.SetOutput(new(logging.LogWriter))
if Config.LogFile != "" {
logName := Config.LogFile
hostname := os.Getenv("HOSTNAME")
if hostname == "" {
hostname, err = os.Hostname()
if err != nil {
hostname = "localhost"
}
}
if strings.HasSuffix(logName, ".log") {
logName = fmt.Sprintf("%s-%s.log", strings.Split(logName, ".log")[0], hostname)
} else {
// it is log dir
logName = fmt.Sprintf("%s/%s.log", logName, hostname)
}
logName = strings.Replace(logName, "//", "/", -1)
// rl, err := rotatelogs.New(Config.LogFile + "-%Y%m%d")
rl, err := rotatelogs.New(logName + "-%Y%m%d")
if err == nil {
rotlogs := logging.RotateLogWriter{RotateLogs: rl}
log.SetOutput(rotlogs)
} else {
log.Println("ERROR: unable to get rotatelogs", err)
}
}
if err != nil {
log.Printf("Unable to parse, time: %v, config: %v\n", time.Now(), configFile)
}
log.Println("Configuration:", Config.String())
// initialize cmsauth layer
CMSAuth.Init(Config.Hmac)
// initialize limiter
initLimiter(Config.LimiterPeriod)
// initialize templates
tmpl := make(TmplRecord)
tmpl["Base"] = Config.Base
tmpl["ServerInfo"] = ServerInfo
tmpl["Time"] = time.Now()
_top = template.HTML(tmplPage("top.tmpl", tmpl))
_bottom = template.HTML(tmplPage("bottom.tmpl", tmpl))
_header = template.HTML(tmplPage("header.tmpl", tmpl))
_footer = template.HTML(tmplPage("footer.tmpl", tmpl))
// static handlers
for _, dir := range []string{"js", "css", "images"} {
m := fmt.Sprintf("%s/%s/", Config.Base, dir)
d := fmt.Sprintf("%s/%s", Config.StaticDir, dir)
http.Handle(m, http.StripPrefix(m, http.FileServer(http.Dir(d))))
}
// setup WMStatsManager to handle our cache
wMgr = NewWMStatsManager(Config.AccessURI)
ctx0, cancel0 := context.WithCancel(context.Background())
defer cancel0()
go updateWMStatsCache(wMgr, ctx0)
// define our HTTP server
http.Handle("/", Handlers())
addr := fmt.Sprintf(":%d", Config.Port)
server := &http.Server{
Addr: addr,
}
// make extra channel for graceful shutdown
// https://medium.com/honestbee-tw-engineer/gracefully-shutdown-in-go-http-server-5f5e6b83da5a
httpDone := make(chan os.Signal, 1)
signal.Notify(httpDone, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
// start necessary HTTP servers
go func() {
// Start either HTTPs or HTTP web server
_, e1 := os.Stat(Config.ServerCrt)
_, e2 := os.Stat(Config.ServerKey)
if e1 == nil && e2 == nil {
//start HTTPS server which require user certificates
rootCA := x509.NewCertPool()
caCert, _ := ioutil.ReadFile(Config.RootCA)
rootCA.AppendCertsFromPEM(caCert)
server = &http.Server{
Addr: addr,
TLSConfig: &tls.Config{
// ClientAuth: tls.RequestClientCert,
RootCAs: rootCA,
},
}
log.Printf("Starting HTTPs server at %v", addr)
err = server.ListenAndServeTLS(Config.ServerCrt, Config.ServerKey)
} else {
// Start server without user certificates
log.Printf("Starting HTTP server at %s", addr)
err = server.ListenAndServe()
}
if err != nil {
log.Printf("Fail to start server %v", err)
}
}()
// properly stop our HTTP and Migration Servers
<-httpDone
log.Print("HTTP server stopped")
// add extra timeout for shutdown service stuff
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer func() {
cancel()
}()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Server Shutdown Failed:%+v", err)
}
log.Print("HTTP server exited properly")
}