github.com/astaxie/beego@v1.12.3/admin.go (about) 1 // Copyright 2014 beego Author. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package beego 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "net/http" 22 "os" 23 "reflect" 24 "strconv" 25 "text/template" 26 "time" 27 28 "github.com/prometheus/client_golang/prometheus/promhttp" 29 30 "github.com/astaxie/beego/grace" 31 "github.com/astaxie/beego/logs" 32 "github.com/astaxie/beego/toolbox" 33 "github.com/astaxie/beego/utils" 34 ) 35 36 // BeeAdminApp is the default adminApp used by admin module. 37 var beeAdminApp *adminApp 38 39 // FilterMonitorFunc is default monitor filter when admin module is enable. 40 // if this func returns, admin module records qps for this request by condition of this function logic. 41 // usage: 42 // func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool { 43 // if method == "POST" { 44 // return false 45 // } 46 // if t.Nanoseconds() < 100 { 47 // return false 48 // } 49 // if strings.HasPrefix(requestPath, "/astaxie") { 50 // return false 51 // } 52 // return true 53 // } 54 // beego.FilterMonitorFunc = MyFilterMonitor. 55 var FilterMonitorFunc func(string, string, time.Duration, string, int) bool 56 57 func init() { 58 beeAdminApp = &adminApp{ 59 routers: make(map[string]http.HandlerFunc), 60 } 61 // keep in mind that all data should be html escaped to avoid XSS attack 62 beeAdminApp.Route("/", adminIndex) 63 beeAdminApp.Route("/qps", qpsIndex) 64 beeAdminApp.Route("/prof", profIndex) 65 beeAdminApp.Route("/healthcheck", healthcheck) 66 beeAdminApp.Route("/task", taskStatus) 67 beeAdminApp.Route("/listconf", listConf) 68 beeAdminApp.Route("/metrics", promhttp.Handler().ServeHTTP) 69 FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true } 70 } 71 72 // AdminIndex is the default http.Handler for admin module. 73 // it matches url pattern "/". 74 func adminIndex(rw http.ResponseWriter, _ *http.Request) { 75 writeTemplate(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl) 76 } 77 78 // QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter. 79 // it's registered with url pattern "/qps" in admin module. 80 func qpsIndex(rw http.ResponseWriter, _ *http.Request) { 81 data := make(map[interface{}]interface{}) 82 data["Content"] = toolbox.StatisticsMap.GetMap() 83 84 // do html escape before display path, avoid xss 85 if content, ok := (data["Content"]).(M); ok { 86 if resultLists, ok := (content["Data"]).([][]string); ok { 87 for i := range resultLists { 88 if len(resultLists[i]) > 0 { 89 resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0]) 90 } 91 } 92 } 93 } 94 95 writeTemplate(rw, data, qpsTpl, defaultScriptsTpl) 96 } 97 98 // ListConf is the http.Handler of displaying all beego configuration values as key/value pair. 99 // it's registered with url pattern "/listconf" in admin module. 100 func listConf(rw http.ResponseWriter, r *http.Request) { 101 r.ParseForm() 102 command := r.Form.Get("command") 103 if command == "" { 104 rw.Write([]byte("command not support")) 105 return 106 } 107 108 data := make(map[interface{}]interface{}) 109 switch command { 110 case "conf": 111 m := make(M) 112 list("BConfig", BConfig, m) 113 m["AppConfigPath"] = template.HTMLEscapeString(appConfigPath) 114 m["AppConfigProvider"] = template.HTMLEscapeString(appConfigProvider) 115 tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) 116 tmpl = template.Must(tmpl.Parse(configTpl)) 117 tmpl = template.Must(tmpl.Parse(defaultScriptsTpl)) 118 119 data["Content"] = m 120 121 tmpl.Execute(rw, data) 122 123 case "router": 124 content := PrintTree() 125 content["Fields"] = []string{ 126 "Router Pattern", 127 "Methods", 128 "Controller", 129 } 130 data["Content"] = content 131 data["Title"] = "Routers" 132 writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl) 133 case "filter": 134 var ( 135 content = M{ 136 "Fields": []string{ 137 "Router Pattern", 138 "Filter Function", 139 }, 140 } 141 filterTypes = []string{} 142 filterTypeData = make(M) 143 ) 144 145 if BeeApp.Handlers.enableFilter { 146 var filterType string 147 for k, fr := range map[int]string{ 148 BeforeStatic: "Before Static", 149 BeforeRouter: "Before Router", 150 BeforeExec: "Before Exec", 151 AfterExec: "After Exec", 152 FinishRouter: "Finish Router"} { 153 if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 { 154 filterType = fr 155 filterTypes = append(filterTypes, filterType) 156 resultList := new([][]string) 157 for _, f := range bf { 158 var result = []string{ 159 // void xss 160 template.HTMLEscapeString(f.pattern), 161 template.HTMLEscapeString(utils.GetFuncName(f.filterFunc)), 162 } 163 *resultList = append(*resultList, result) 164 } 165 filterTypeData[filterType] = resultList 166 } 167 } 168 } 169 170 content["Data"] = filterTypeData 171 content["Methods"] = filterTypes 172 173 data["Content"] = content 174 data["Title"] = "Filters" 175 writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl) 176 default: 177 rw.Write([]byte("command not support")) 178 } 179 } 180 181 func list(root string, p interface{}, m M) { 182 pt := reflect.TypeOf(p) 183 pv := reflect.ValueOf(p) 184 if pt.Kind() == reflect.Ptr { 185 pt = pt.Elem() 186 pv = pv.Elem() 187 } 188 for i := 0; i < pv.NumField(); i++ { 189 var key string 190 if root == "" { 191 key = pt.Field(i).Name 192 } else { 193 key = root + "." + pt.Field(i).Name 194 } 195 if pv.Field(i).Kind() == reflect.Struct { 196 list(key, pv.Field(i).Interface(), m) 197 } else { 198 m[key] = pv.Field(i).Interface() 199 } 200 } 201 } 202 203 // PrintTree prints all registered routers. 204 func PrintTree() M { 205 var ( 206 content = M{} 207 methods = []string{} 208 methodsData = make(M) 209 ) 210 for method, t := range BeeApp.Handlers.routers { 211 212 resultList := new([][]string) 213 214 printTree(resultList, t) 215 216 methods = append(methods, template.HTMLEscapeString(method)) 217 methodsData[template.HTMLEscapeString(method)] = resultList 218 } 219 220 content["Data"] = methodsData 221 content["Methods"] = methods 222 return content 223 } 224 225 func printTree(resultList *[][]string, t *Tree) { 226 for _, tr := range t.fixrouters { 227 printTree(resultList, tr) 228 } 229 if t.wildcard != nil { 230 printTree(resultList, t.wildcard) 231 } 232 for _, l := range t.leaves { 233 if v, ok := l.runObject.(*ControllerInfo); ok { 234 if v.routerType == routerTypeBeego { 235 var result = []string{ 236 template.HTMLEscapeString(v.pattern), 237 template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)), 238 template.HTMLEscapeString(v.controllerType.String()), 239 } 240 *resultList = append(*resultList, result) 241 } else if v.routerType == routerTypeRESTFul { 242 var result = []string{ 243 template.HTMLEscapeString(v.pattern), 244 template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)), 245 "", 246 } 247 *resultList = append(*resultList, result) 248 } else if v.routerType == routerTypeHandler { 249 var result = []string{ 250 template.HTMLEscapeString(v.pattern), 251 "", 252 "", 253 } 254 *resultList = append(*resultList, result) 255 } 256 } 257 } 258 } 259 260 // ProfIndex is a http.Handler for showing profile command. 261 // it's in url pattern "/prof" in admin module. 262 func profIndex(rw http.ResponseWriter, r *http.Request) { 263 r.ParseForm() 264 command := r.Form.Get("command") 265 if command == "" { 266 return 267 } 268 269 var ( 270 format = r.Form.Get("format") 271 data = make(map[interface{}]interface{}) 272 result bytes.Buffer 273 ) 274 toolbox.ProcessInput(command, &result) 275 data["Content"] = template.HTMLEscapeString(result.String()) 276 277 if format == "json" && command == "gc summary" { 278 dataJSON, err := json.Marshal(data) 279 if err != nil { 280 http.Error(rw, err.Error(), http.StatusInternalServerError) 281 return 282 } 283 writeJSON(rw, dataJSON) 284 return 285 } 286 287 data["Title"] = template.HTMLEscapeString(command) 288 defaultTpl := defaultScriptsTpl 289 if command == "gc summary" { 290 defaultTpl = gcAjaxTpl 291 } 292 writeTemplate(rw, data, profillingTpl, defaultTpl) 293 } 294 295 // Healthcheck is a http.Handler calling health checking and showing the result. 296 // it's in "/healthcheck" pattern in admin module. 297 func healthcheck(rw http.ResponseWriter, r *http.Request) { 298 var ( 299 result []string 300 data = make(map[interface{}]interface{}) 301 resultList = new([][]string) 302 content = M{ 303 "Fields": []string{"Name", "Message", "Status"}, 304 } 305 ) 306 307 for name, h := range toolbox.AdminCheckList { 308 if err := h.Check(); err != nil { 309 result = []string{ 310 "error", 311 template.HTMLEscapeString(name), 312 template.HTMLEscapeString(err.Error()), 313 } 314 } else { 315 result = []string{ 316 "success", 317 template.HTMLEscapeString(name), 318 "OK", 319 } 320 } 321 *resultList = append(*resultList, result) 322 } 323 324 queryParams := r.URL.Query() 325 jsonFlag := queryParams.Get("json") 326 shouldReturnJSON, _ := strconv.ParseBool(jsonFlag) 327 328 if shouldReturnJSON { 329 response := buildHealthCheckResponseList(resultList) 330 jsonResponse, err := json.Marshal(response) 331 332 if err != nil { 333 http.Error(rw, err.Error(), http.StatusInternalServerError) 334 } else { 335 writeJSON(rw, jsonResponse) 336 } 337 return 338 } 339 340 content["Data"] = resultList 341 data["Content"] = content 342 data["Title"] = "Health Check" 343 344 writeTemplate(rw, data, healthCheckTpl, defaultScriptsTpl) 345 } 346 347 func buildHealthCheckResponseList(healthCheckResults *[][]string) []map[string]interface{} { 348 response := make([]map[string]interface{}, len(*healthCheckResults)) 349 350 for i, healthCheckResult := range *healthCheckResults { 351 currentResultMap := make(map[string]interface{}) 352 353 currentResultMap["name"] = healthCheckResult[0] 354 currentResultMap["message"] = healthCheckResult[1] 355 currentResultMap["status"] = healthCheckResult[2] 356 357 response[i] = currentResultMap 358 } 359 360 return response 361 362 } 363 364 func writeJSON(rw http.ResponseWriter, jsonData []byte) { 365 rw.Header().Set("Content-Type", "application/json") 366 rw.Write(jsonData) 367 } 368 369 // TaskStatus is a http.Handler with running task status (task name, status and the last execution). 370 // it's in "/task" pattern in admin module. 371 func taskStatus(rw http.ResponseWriter, req *http.Request) { 372 data := make(map[interface{}]interface{}) 373 374 // Run Task 375 req.ParseForm() 376 taskname := req.Form.Get("taskname") 377 if taskname != "" { 378 if t, ok := toolbox.AdminTaskList[taskname]; ok { 379 if err := t.Run(); err != nil { 380 data["Message"] = []string{"error", template.HTMLEscapeString(fmt.Sprintf("%s", err))} 381 } 382 data["Message"] = []string{"success", template.HTMLEscapeString(fmt.Sprintf("%s run success,Now the Status is <br>%s", taskname, t.GetStatus()))} 383 } else { 384 data["Message"] = []string{"warning", template.HTMLEscapeString(fmt.Sprintf("there's no task which named: %s", taskname))} 385 } 386 } 387 388 // List Tasks 389 content := make(M) 390 resultList := new([][]string) 391 var fields = []string{ 392 "Task Name", 393 "Task Spec", 394 "Task Status", 395 "Last Time", 396 "", 397 } 398 for tname, tk := range toolbox.AdminTaskList { 399 result := []string{ 400 template.HTMLEscapeString(tname), 401 template.HTMLEscapeString(tk.GetSpec()), 402 template.HTMLEscapeString(tk.GetStatus()), 403 template.HTMLEscapeString(tk.GetPrev().String()), 404 } 405 *resultList = append(*resultList, result) 406 } 407 408 content["Fields"] = fields 409 content["Data"] = resultList 410 data["Content"] = content 411 data["Title"] = "Tasks" 412 writeTemplate(rw, data, tasksTpl, defaultScriptsTpl) 413 } 414 415 func writeTemplate(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) { 416 tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) 417 for _, tpl := range tpls { 418 tmpl = template.Must(tmpl.Parse(tpl)) 419 } 420 tmpl.Execute(rw, data) 421 } 422 423 // adminApp is an http.HandlerFunc map used as beeAdminApp. 424 type adminApp struct { 425 routers map[string]http.HandlerFunc 426 } 427 428 // Route adds http.HandlerFunc to adminApp with url pattern. 429 func (admin *adminApp) Route(pattern string, f http.HandlerFunc) { 430 admin.routers[pattern] = f 431 } 432 433 // Run adminApp http server. 434 // Its addr is defined in configuration file as adminhttpaddr and adminhttpport. 435 func (admin *adminApp) Run() { 436 if len(toolbox.AdminTaskList) > 0 { 437 toolbox.StartTask() 438 } 439 addr := BConfig.Listen.AdminAddr 440 441 if BConfig.Listen.AdminPort != 0 { 442 addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort) 443 } 444 for p, f := range admin.routers { 445 http.Handle(p, f) 446 } 447 logs.Info("Admin server Running on %s", addr) 448 449 var err error 450 if BConfig.Listen.Graceful { 451 err = grace.ListenAndServe(addr, nil) 452 } else { 453 err = http.ListenAndServe(addr, nil) 454 } 455 if err != nil { 456 logs.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid())) 457 } 458 }