admin.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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. package beego
  15. import (
  16. "bytes"
  17. "encoding/json"
  18. "fmt"
  19. "net/http"
  20. "os"
  21. "text/template"
  22. "time"
  23. "reflect"
  24. "github.com/cnlh/nps/vender/github.com/astaxie/beego/grace"
  25. "github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
  26. "github.com/cnlh/nps/vender/github.com/astaxie/beego/toolbox"
  27. "github.com/cnlh/nps/vender/github.com/astaxie/beego/utils"
  28. )
  29. // BeeAdminApp is the default adminApp used by admin module.
  30. var beeAdminApp *adminApp
  31. // FilterMonitorFunc is default monitor filter when admin module is enable.
  32. // if this func returns, admin module records qbs for this request by condition of this function logic.
  33. // usage:
  34. // func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool {
  35. // if method == "POST" {
  36. // return false
  37. // }
  38. // if t.Nanoseconds() < 100 {
  39. // return false
  40. // }
  41. // if strings.HasPrefix(requestPath, "/astaxie") {
  42. // return false
  43. // }
  44. // return true
  45. // }
  46. // beego.FilterMonitorFunc = MyFilterMonitor.
  47. var FilterMonitorFunc func(string, string, time.Duration, string, int) bool
  48. func init() {
  49. beeAdminApp = &adminApp{
  50. routers: make(map[string]http.HandlerFunc),
  51. }
  52. beeAdminApp.Route("/", adminIndex)
  53. beeAdminApp.Route("/qps", qpsIndex)
  54. beeAdminApp.Route("/prof", profIndex)
  55. beeAdminApp.Route("/healthcheck", healthcheck)
  56. beeAdminApp.Route("/task", taskStatus)
  57. beeAdminApp.Route("/listconf", listConf)
  58. FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true }
  59. }
  60. // AdminIndex is the default http.Handler for admin module.
  61. // it matches url pattern "/".
  62. func adminIndex(rw http.ResponseWriter, _ *http.Request) {
  63. execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
  64. }
  65. // QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter.
  66. // it's registered with url pattern "/qbs" in admin module.
  67. func qpsIndex(rw http.ResponseWriter, _ *http.Request) {
  68. data := make(map[interface{}]interface{})
  69. data["Content"] = toolbox.StatisticsMap.GetMap()
  70. // do html escape before display path, avoid xss
  71. if content, ok := (data["Content"]).(M); ok {
  72. if resultLists, ok := (content["Data"]).([][]string); ok {
  73. for i := range resultLists {
  74. if len(resultLists[i]) > 0 {
  75. resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0])
  76. }
  77. }
  78. }
  79. }
  80. execTpl(rw, data, qpsTpl, defaultScriptsTpl)
  81. }
  82. // ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
  83. // it's registered with url pattern "/listconf" in admin module.
  84. func listConf(rw http.ResponseWriter, r *http.Request) {
  85. r.ParseForm()
  86. command := r.Form.Get("command")
  87. if command == "" {
  88. rw.Write([]byte("command not support"))
  89. return
  90. }
  91. data := make(map[interface{}]interface{})
  92. switch command {
  93. case "conf":
  94. m := make(M)
  95. list("BConfig", BConfig, m)
  96. m["AppConfigPath"] = appConfigPath
  97. m["AppConfigProvider"] = appConfigProvider
  98. tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
  99. tmpl = template.Must(tmpl.Parse(configTpl))
  100. tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
  101. data["Content"] = m
  102. tmpl.Execute(rw, data)
  103. case "router":
  104. content := PrintTree()
  105. content["Fields"] = []string{
  106. "Router Pattern",
  107. "Methods",
  108. "Controller",
  109. }
  110. data["Content"] = content
  111. data["Title"] = "Routers"
  112. execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
  113. case "filter":
  114. var (
  115. content = M{
  116. "Fields": []string{
  117. "Router Pattern",
  118. "Filter Function",
  119. },
  120. }
  121. filterTypes = []string{}
  122. filterTypeData = make(M)
  123. )
  124. if BeeApp.Handlers.enableFilter {
  125. var filterType string
  126. for k, fr := range map[int]string{
  127. BeforeStatic: "Before Static",
  128. BeforeRouter: "Before Router",
  129. BeforeExec: "Before Exec",
  130. AfterExec: "After Exec",
  131. FinishRouter: "Finish Router"} {
  132. if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 {
  133. filterType = fr
  134. filterTypes = append(filterTypes, filterType)
  135. resultList := new([][]string)
  136. for _, f := range bf {
  137. var result = []string{
  138. f.pattern,
  139. utils.GetFuncName(f.filterFunc),
  140. }
  141. *resultList = append(*resultList, result)
  142. }
  143. filterTypeData[filterType] = resultList
  144. }
  145. }
  146. }
  147. content["Data"] = filterTypeData
  148. content["Methods"] = filterTypes
  149. data["Content"] = content
  150. data["Title"] = "Filters"
  151. execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
  152. default:
  153. rw.Write([]byte("command not support"))
  154. }
  155. }
  156. func list(root string, p interface{}, m M) {
  157. pt := reflect.TypeOf(p)
  158. pv := reflect.ValueOf(p)
  159. if pt.Kind() == reflect.Ptr {
  160. pt = pt.Elem()
  161. pv = pv.Elem()
  162. }
  163. for i := 0; i < pv.NumField(); i++ {
  164. var key string
  165. if root == "" {
  166. key = pt.Field(i).Name
  167. } else {
  168. key = root + "." + pt.Field(i).Name
  169. }
  170. if pv.Field(i).Kind() == reflect.Struct {
  171. list(key, pv.Field(i).Interface(), m)
  172. } else {
  173. m[key] = pv.Field(i).Interface()
  174. }
  175. }
  176. }
  177. // PrintTree prints all registered routers.
  178. func PrintTree() M {
  179. var (
  180. content = M{}
  181. methods = []string{}
  182. methodsData = make(M)
  183. )
  184. for method, t := range BeeApp.Handlers.routers {
  185. resultList := new([][]string)
  186. printTree(resultList, t)
  187. methods = append(methods, method)
  188. methodsData[method] = resultList
  189. }
  190. content["Data"] = methodsData
  191. content["Methods"] = methods
  192. return content
  193. }
  194. func printTree(resultList *[][]string, t *Tree) {
  195. for _, tr := range t.fixrouters {
  196. printTree(resultList, tr)
  197. }
  198. if t.wildcard != nil {
  199. printTree(resultList, t.wildcard)
  200. }
  201. for _, l := range t.leaves {
  202. if v, ok := l.runObject.(*ControllerInfo); ok {
  203. if v.routerType == routerTypeBeego {
  204. var result = []string{
  205. v.pattern,
  206. fmt.Sprintf("%s", v.methods),
  207. v.controllerType.String(),
  208. }
  209. *resultList = append(*resultList, result)
  210. } else if v.routerType == routerTypeRESTFul {
  211. var result = []string{
  212. v.pattern,
  213. fmt.Sprintf("%s", v.methods),
  214. "",
  215. }
  216. *resultList = append(*resultList, result)
  217. } else if v.routerType == routerTypeHandler {
  218. var result = []string{
  219. v.pattern,
  220. "",
  221. "",
  222. }
  223. *resultList = append(*resultList, result)
  224. }
  225. }
  226. }
  227. }
  228. // ProfIndex is a http.Handler for showing profile command.
  229. // it's in url pattern "/prof" in admin module.
  230. func profIndex(rw http.ResponseWriter, r *http.Request) {
  231. r.ParseForm()
  232. command := r.Form.Get("command")
  233. if command == "" {
  234. return
  235. }
  236. var (
  237. format = r.Form.Get("format")
  238. data = make(map[interface{}]interface{})
  239. result bytes.Buffer
  240. )
  241. toolbox.ProcessInput(command, &result)
  242. data["Content"] = result.String()
  243. if format == "json" && command == "gc summary" {
  244. dataJSON, err := json.Marshal(data)
  245. if err != nil {
  246. http.Error(rw, err.Error(), http.StatusInternalServerError)
  247. return
  248. }
  249. rw.Header().Set("Content-Type", "application/json")
  250. rw.Write(dataJSON)
  251. return
  252. }
  253. data["Title"] = command
  254. defaultTpl := defaultScriptsTpl
  255. if command == "gc summary" {
  256. defaultTpl = gcAjaxTpl
  257. }
  258. execTpl(rw, data, profillingTpl, defaultTpl)
  259. }
  260. // Healthcheck is a http.Handler calling health checking and showing the result.
  261. // it's in "/healthcheck" pattern in admin module.
  262. func healthcheck(rw http.ResponseWriter, _ *http.Request) {
  263. var (
  264. result []string
  265. data = make(map[interface{}]interface{})
  266. resultList = new([][]string)
  267. content = M{
  268. "Fields": []string{"Name", "Message", "Status"},
  269. }
  270. )
  271. for name, h := range toolbox.AdminCheckList {
  272. if err := h.Check(); err != nil {
  273. result = []string{
  274. "error",
  275. name,
  276. err.Error(),
  277. }
  278. } else {
  279. result = []string{
  280. "success",
  281. name,
  282. "OK",
  283. }
  284. }
  285. *resultList = append(*resultList, result)
  286. }
  287. content["Data"] = resultList
  288. data["Content"] = content
  289. data["Title"] = "Health Check"
  290. execTpl(rw, data, healthCheckTpl, defaultScriptsTpl)
  291. }
  292. // TaskStatus is a http.Handler with running task status (task name, status and the last execution).
  293. // it's in "/task" pattern in admin module.
  294. func taskStatus(rw http.ResponseWriter, req *http.Request) {
  295. data := make(map[interface{}]interface{})
  296. // Run Task
  297. req.ParseForm()
  298. taskname := req.Form.Get("taskname")
  299. if taskname != "" {
  300. if t, ok := toolbox.AdminTaskList[taskname]; ok {
  301. if err := t.Run(); err != nil {
  302. data["Message"] = []string{"error", fmt.Sprintf("%s", err)}
  303. }
  304. data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is <br>%s", taskname, t.GetStatus())}
  305. } else {
  306. data["Message"] = []string{"warning", fmt.Sprintf("there's no task which named: %s", taskname)}
  307. }
  308. }
  309. // List Tasks
  310. content := make(M)
  311. resultList := new([][]string)
  312. var fields = []string{
  313. "Task Name",
  314. "Task Spec",
  315. "Task Status",
  316. "Last Time",
  317. "",
  318. }
  319. for tname, tk := range toolbox.AdminTaskList {
  320. result := []string{
  321. tname,
  322. tk.GetSpec(),
  323. tk.GetStatus(),
  324. tk.GetPrev().String(),
  325. }
  326. *resultList = append(*resultList, result)
  327. }
  328. content["Fields"] = fields
  329. content["Data"] = resultList
  330. data["Content"] = content
  331. data["Title"] = "Tasks"
  332. execTpl(rw, data, tasksTpl, defaultScriptsTpl)
  333. }
  334. func execTpl(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) {
  335. tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
  336. for _, tpl := range tpls {
  337. tmpl = template.Must(tmpl.Parse(tpl))
  338. }
  339. tmpl.Execute(rw, data)
  340. }
  341. // adminApp is an http.HandlerFunc map used as beeAdminApp.
  342. type adminApp struct {
  343. routers map[string]http.HandlerFunc
  344. }
  345. // Route adds http.HandlerFunc to adminApp with url pattern.
  346. func (admin *adminApp) Route(pattern string, f http.HandlerFunc) {
  347. admin.routers[pattern] = f
  348. }
  349. // Run adminApp http server.
  350. // Its addr is defined in configuration file as adminhttpaddr and adminhttpport.
  351. func (admin *adminApp) Run() {
  352. if len(toolbox.AdminTaskList) > 0 {
  353. toolbox.StartTask()
  354. }
  355. addr := BConfig.Listen.AdminAddr
  356. if BConfig.Listen.AdminPort != 0 {
  357. addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort)
  358. }
  359. for p, f := range admin.routers {
  360. http.Handle(p, f)
  361. }
  362. logs.Info("Admin server Running on %s", addr)
  363. var err error
  364. if BConfig.Listen.Graceful {
  365. err = grace.ListenAndServe(addr, nil)
  366. } else {
  367. err = http.ListenAndServe(addr, nil)
  368. }
  369. if err != nil {
  370. logs.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
  371. }
  372. }