This repository has been archived by the owner on Apr 10, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathhttp-api.go
322 lines (287 loc) · 11.5 KB
/
http-api.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
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
// HTTP API to control probe service for representing collected data.
package main
import (
"crypto/sha1"
"encoding/base64"
"expvar"
"fmt"
"github.com/gorilla/mux"
"net/http"
"os"
"strconv"
"strings"
"text/template"
"time"
)
var Page *template.Template
// Elder.
func HttpAPI() {
var err error
Page, err = template.ParseGlob("templates/*.tmpl")
if err != nil {
fmt.Printf("Error in template with error %s", err)
os.Exit(1)
}
r := mux.NewRouter()
r.HandleFunc("/debug", HandleHTTP(expvarHandler)).Methods("GET", "HEAD")
r.HandleFunc("/", HandleHTTP(rootAPI)).Methods("GET", "HEAD")
/* Monitoring interface (for humans and robots)
*/
// Show stream list for all groups
r.HandleFunc("/act", HandleHTTP(ActivityIndex)).Methods("GET")
// Show stream list for the group
r.HandleFunc("/act/{group}", HandleHTTP(ActivityIndex)).Methods("GET")
// Информация о потоке и сводная статистика
r.HandleFunc("/act/{group}/{stream}", HandleHTTP(ActivityStreamInfo)).Methods("GET")
r.HandleFunc("/act/{group}/{stream}/", HandleHTTP(ActivityStreamInfo)).Methods("GET")
// История ошибок
r.HandleFunc("/act/{group}/{stream}/{mode:history|errors}", HandleHTTP(ActivityStreamHistory)).Methods("GET")
// Вывод результата проверки для мастер-плейлиста
r.HandleFunc("/act/{group}/{stream}/{stamp:[0-9]+}/raw", HandleHTTP(ActivityStreamHistory)).Methods("GET")
// Вывод результата проверки для вложенных проверок
r.HandleFunc("/act/{group}/{stream}/{stamp:[0-9]+}/{idx:[0-9]+}/raw", HandleHTTP(ActivityStreamHistory)).Methods("GET")
/* Zabbix integration
*/
// Discovery data for Zabbix for all groups
r.HandleFunc("/zabbix-discovery", HandleHTTP(zabbixDiscovery())).Methods("GET", "HEAD")
r.HandleFunc("/zabbix-discovery/{group}", HandleHTTP(zabbixDiscovery())).Methods("GET", "HEAD")
// строковое значение ошибки для выбранных группы и канала
r.HandleFunc("/mon/error/{group}/{stream}/{astype:int|str}", HandleHTTP(monError)).Methods("GET", "HEAD")
// числовое значение ошибки для выбранных группы и канала в диапазоне errlevel from-upto
r.HandleFunc("/mon/error/{group}/{stream}/{fromerrlevel:[a-z]+}-{uptoerrlevel:[a-z]+}", HandleHTTP(monErrorLevel)).Methods("GET")
/* Reports for humans
*/
// Вывод описания ошибки из анализатора
r.HandleFunc("/rpt", HandleHTTP(ReportIndex)).Methods("GET")
r.HandleFunc("/rpt/", HandleHTTP(ReportIndex)).Methods("GET")
r.HandleFunc("/rpt/{rptid:[0-9]+}", HandleHTTP(ReportStreamErrors)).Methods("GET")
// Obsoleted reports with old API:
// r.HandleFunc("/rprt", rprtMainPage).Methods("GET")
// r.HandleFunc("/rprt/3hours", rprt3Hours).Methods("GET")
// r.HandleFunc("/rprt/last", rprtLast).Methods("GET")
// r.HandleFunc("/rprt/last-critical", rprtLastCritical).Methods("GET")
// r.HandleFunc("/rprt/g/{group}", rprtGroup).Methods("GET")
// r.HandleFunc("/rprt/g/{group}/last", rprtGroupLast).Methods("GET")
// r.HandleFunc("/rprt/g/{group}/last-critical", rprtGroupLastCritical).Methods("GET")
// r.HandleFunc("/zabbix", zabbixStatus).Methods("GET", "HEAD") // text report for all groups to Zabbix
// r.HandleFunc("/mon/status/{group}", zabbixStatus).Methods("GET", "HEAD") // text report for selected group to Zabbix
// r.HandleFunc("/mon/status/{group}/{stream}", zabbixStatus).Methods("GET", "HEAD") // text report for selected group to Zabbix
// Zabbix autodiscovery protocol (JSON)
// https://www.zabbix.com/documentation/ru/2.0/manual/discovery/low_level_discovery
// new API
/* Misc static data. Unauthorized access allowed.
*/
r.Handle("/css/{{name}}.css", http.FileServer(http.Dir("bootstrap"))).Methods("GET", "HEAD")
r.Handle("/js/{{name}}.js", http.FileServer(http.Dir("bootstrap"))).Methods("GET", "HEAD")
r.Handle("/{{name}}.png", http.FileServer(http.Dir("pics"))).Methods("GET", "HEAD")
r.Handle("/favicon.ico", http.FileServer(http.Dir("pics"))).Methods("GET", "HEAD")
fmt.Printf("Listen for API connections at %s\n", cfg.ListenHTTP)
srv := &http.Server{
Addr: cfg.ListenHTTP,
Handler: r,
ReadTimeout: 30 * time.Second,
}
srv.ListenAndServe()
}
func rootAPI(res http.ResponseWriter, req *http.Request, vars map[string]string) {
data := make(map[string]interface{})
data["title"] = cfg.Stubs.Name
data["monState"] = StatsGlobals.MonitoringState
data["totalMonPoints"] = StatsGlobals.TotalMonitoringPoints
data["totalHLSMonPoints"] = StatsGlobals.TotalHLSMonitoringPoints
data["totalHDSMonPoints"] = StatsGlobals.TotalHDSMonitoringPoints
data["totalHTTPMonPoints"] = StatsGlobals.TotalHTTPMonitoringPoints
Page.ExecuteTemplate(res, "index", data)
}
// Webhandler. Возвращает text/plain значение ошибки для выбранных группы и канала.
func monError(res http.ResponseWriter, req *http.Request, vars map[string]string) {
res.Header().Set("Server", SERVER)
res.Header().Set("Content-Type", "text/plain")
if vars["group"] != "" && vars["stream"] != "" {
if !StatsGlobals.MonitoringState {
switch vars["astype"] { // пока мониторинг остановлен, считаем, что всё ок
case "str":
res.Write([]byte("success"))
case "int":
res.Write([]byte("0"))
}
return
}
if result, err := LoadLastResult(Key{vars["group"], vars["stream"]}); err == nil {
switch vars["astype"] {
case "str":
res.Write([]byte(StreamErr2String(result.ErrType)))
case "int":
res.Write([]byte(strconv.Itoa(int(result.ErrType))))
}
} else { // пока проверки не проводились, считаем, что всё ок. Чего зря беспокоиться?
switch vars["astype"] {
case "str":
res.Write([]byte("success"))
case "int":
res.Write([]byte("0"))
}
}
} else {
http.Error(res, "Bad parameters in query.", http.StatusBadRequest)
}
}
// Webhandler. Возвращает text/plain значение ошибки для выбранных группы и канала.
// Если ошибка ниже заданного мин.уровня, то выдаётся 0=OK, если в границах указанных уровней, то 1=PROBLEM,
// если выше макс.уровня, то 2=FATAL
func monErrorLevel(res http.ResponseWriter, req *http.Request, vars map[string]string) {
res.Header().Set("Server", SERVER)
res.Header().Set("Content-Type", "text/plain")
if vars["group"] != "" && vars["stream"] != "" && vars["fromerrlevel"] != "" && vars["uptoerrlevel"] != "" {
if !StatsGlobals.MonitoringState {
res.Write([]byte("0")) // пока мониторинг остановлен, считаем, что всё ок
return
}
if result, err := LoadLastResult(Key{vars["group"], vars["stream"]}); err == nil {
cur := result.ErrType
switch {
case cur <= String2StreamErr(vars["fromerrlevel"]):
res.Write([]byte("0")) // OK
case cur >= String2StreamErr(vars["uptoerrlevel"]):
res.Write([]byte("2")) // FATAL
default:
res.Write([]byte("1")) // PROBLEM
}
} else {
res.Write([]byte("0")) // пока проверки не проводились, считаем, что всё ок. Чего зря беспокоиться?
//http.Error(res, "Result not found. Probably stream not tested yet.", http.StatusNotFound)
}
} else {
http.Error(res, "Bad parameters in query.", http.StatusBadRequest)
}
}
// func rprtMainPage(res http.ResponseWriter, req *http.Request) {
// res.Header().Set("Server", SERVER)
// res.Write(ReportMainPage())
// }
// func rprtGroupAll(res http.ResponseWriter, req *http.Request) {
// res.Header().Set("Server", SERVER)
// res.Write([]byte("Сводный отчёт по группам."))
// }
// func rprtGroup(res http.ResponseWriter, req *http.Request) {
// res.Header().Set("Server", SERVER)
// res.Write([]byte("HLS Probe at service."))
// }
// // Group errors report
// func rprtGroupLast(res http.ResponseWriter, req *http.Request) {
// res.Header().Set("Server", SERVER)
// res.Write(ReportLast(mux.Vars(req), false))
// }
// // Group errors report
// func rprtGroupLastCritical(res http.ResponseWriter, req *http.Request) {
// res.Header().Set("Server", SERVER)
// res.Write(ReportLast(mux.Vars(req), true))
// }
// // Group errors report
// func rprt3Hours(res http.ResponseWriter, req *http.Request) {
// res.Header().Set("Server", SERVER)
// res.Write(Report3Hours(mux.Vars(req)))
// }
// // Group errors report
// func rprtLast(res http.ResponseWriter, req *http.Request) {
// res.Header().Set("Server", SERVER)
// res.Write(ReportLast(mux.Vars(req), false))
// }
// // Group errors report
// func rprtLastCritical(res http.ResponseWriter, req *http.Request) {
// res.Header().Set("Server", SERVER)
// res.Write(ReportLast(mux.Vars(req), true))
// }
// Zabbix integration
// func zabbixStatus(res http.ResponseWriter, req *http.Request) {
// res.Header().Set("Server", SERVER)
// res.Write(ZabbixStatus(mux.Vars(req)))
// }
// Zabbix integration (with cfg curried)
func zabbixDiscovery() func(http.ResponseWriter, *http.Request, map[string]string) {
return func(res http.ResponseWriter, req *http.Request, vars map[string]string) {
res.Write(ZabbixDiscoveryWeb(vars))
}
}
func expvarHandler(w http.ResponseWriter, r *http.Request, vars map[string]string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\n")
first := true
expvar.Do(func(kv expvar.KeyValue) {
if !first {
fmt.Fprintf(w, ",\n")
}
first = false
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
})
fmt.Fprintf(w, "\n}\n")
}
// Wrapper for all HTTP handlers.
// Does authorization and preparation of headers.
func HandleHTTP(f func(http.ResponseWriter, *http.Request, map[string]string)) func(http.ResponseWriter, *http.Request) {
var user string
handler := func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("Server", SURFER)
if cfg.User != "" && cfg.Pass != "" {
user = checkAuth(req)
if user != "" {
resp.Header().Set("X-Authenticated-Username", user)
} else {
requireAuth(resp, req, nil)
return
}
}
vars := mux.Vars(req)
f(resp, req, vars)
}
return handler
}
// Handler for unauthorized access.
func requireAuth(w http.ResponseWriter, r *http.Request, v map[string]string) {
w.Header().Set("WWW-Authenticate", `Basic realm="`+SURFER+`"`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
}
/*
TODO адаптировать вместо использования модуля auth
Checks the username/password combination from the request. Returns
either an empty string (authentication failed) or the name of the
authenticated user.
Supports MD5 and SHA1 password entries
*/
func checkAuth(r *http.Request) string {
var passwd string
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 || s[0] != "Basic" {
return ""
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
return ""
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
return ""
}
if pair[0] == cfg.User {
passwd = cfg.Pass
} else {
return ""
}
if passwd[:5] == "{SHA}" {
d := sha1.New()
d.Write([]byte(pair[1]))
if passwd[5:] != base64.StdEncoding.EncodeToString(d.Sum(nil)) {
return ""
}
} else {
e := NewMD5Entry(passwd)
if e == nil {
return ""
}
if passwd != string(MD5Crypt([]byte(pair[1]), e.Salt, e.Magic)) {
return ""
}
}
return pair[0]
}