http.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "html/template"
  6. "io"
  7. "log"
  8. "net/http"
  9. "runtime"
  10. "sort"
  11. "strconv"
  12. "time"
  13. "github.com/abh/geodns/monitor"
  14. "github.com/abh/geodns/zones"
  15. metrics "github.com/rcrowley/go-metrics"
  16. )
  17. type httpServer struct {
  18. mux *http.ServeMux
  19. zones zones.Zones
  20. serverInfo monitor.ServerInfo
  21. }
  22. type rate struct {
  23. Name string
  24. Count int64
  25. Metrics zones.ZoneMetrics
  26. }
  27. type Rates []*rate
  28. func (s Rates) Len() int { return len(s) }
  29. func (s Rates) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  30. type RatesByCount struct{ Rates }
  31. func (s RatesByCount) Less(i, j int) bool {
  32. ic := s.Rates[i].Count
  33. jc := s.Rates[j].Count
  34. if ic == jc {
  35. return s.Rates[i].Name < s.Rates[j].Name
  36. }
  37. return ic > jc
  38. }
  39. type histogramData struct {
  40. Max int64
  41. Min int64
  42. Mean float64
  43. Pct90 float64
  44. Pct99 float64
  45. Pct999 float64
  46. StdDev float64
  47. }
  48. func setupHistogramData(met metrics.Histogram, dat *histogramData) {
  49. dat.Max = met.Max()
  50. dat.Min = met.Min()
  51. dat.Mean = met.Mean()
  52. dat.StdDev = met.StdDev()
  53. percentiles := met.Percentiles([]float64{0.90, 0.99, 0.999})
  54. dat.Pct90 = percentiles[0]
  55. dat.Pct99 = percentiles[1]
  56. dat.Pct999 = percentiles[2]
  57. }
  58. func topParam(req *http.Request, def int) int {
  59. req.ParseForm()
  60. topOption := def
  61. topParam := req.Form["top"]
  62. if len(topParam) > 0 {
  63. var err error
  64. topOption, err = strconv.Atoi(topParam[0])
  65. if err != nil {
  66. topOption = def
  67. }
  68. }
  69. return topOption
  70. }
  71. func NewHTTPServer(zones zones.Zones) *httpServer {
  72. hs := &httpServer{
  73. // todo: zones.MuxManager instead of zones?
  74. zones: zones,
  75. mux: &http.ServeMux{},
  76. }
  77. hs.mux.HandleFunc("/status", hs.StatusHandler())
  78. hs.mux.HandleFunc("/status.json", hs.StatusJSONHandler())
  79. hs.mux.HandleFunc("/", hs.mainServer)
  80. return hs
  81. }
  82. func (hs *httpServer) Mux() *http.ServeMux {
  83. return hs.mux
  84. }
  85. func (hs *httpServer) Run(listen string) {
  86. log.Println("Starting HTTP interface on", listen)
  87. log.Fatal(http.ListenAndServe(listen, &basicauth{h: hs.mux}))
  88. }
  89. func (hs *httpServer) StatusJSONHandler() func(http.ResponseWriter, *http.Request) {
  90. info := serverInfo
  91. return func(w http.ResponseWriter, req *http.Request) {
  92. zonemetrics := make(map[string]metrics.Registry)
  93. for name, zone := range hs.zones {
  94. zone.Lock()
  95. zonemetrics[name] = zone.Metrics.Registry
  96. zone.Unlock()
  97. }
  98. type statusData struct {
  99. Version string
  100. GoVersion string
  101. Uptime int64
  102. Platform string
  103. Zones map[string]metrics.Registry
  104. Global metrics.Registry
  105. ID string
  106. IP string
  107. UUID string
  108. Groups []string
  109. }
  110. uptime := int64(time.Since(info.Started).Seconds())
  111. status := statusData{
  112. Version: info.Version,
  113. GoVersion: runtime.Version(),
  114. Uptime: uptime,
  115. Platform: runtime.GOARCH + "-" + runtime.GOOS,
  116. Zones: zonemetrics,
  117. Global: metrics.DefaultRegistry,
  118. ID: hs.serverInfo.ID,
  119. IP: hs.serverInfo.IP,
  120. UUID: hs.serverInfo.UUID,
  121. Groups: hs.serverInfo.Groups,
  122. }
  123. b, err := json.Marshal(status)
  124. if err != nil {
  125. http.Error(w, "Error encoding JSON", 500)
  126. return
  127. }
  128. w.Header().Set("Content-Type", "application/json")
  129. w.Write(b)
  130. return
  131. }
  132. }
  133. func (hs *httpServer) StatusHandler() func(http.ResponseWriter, *http.Request) {
  134. return func(w http.ResponseWriter, req *http.Request) {
  135. topOption := topParam(req, 10)
  136. rates := make(Rates, 0)
  137. for name, zone := range hs.zones {
  138. count := zone.Metrics.Queries.Count()
  139. rates = append(rates, &rate{
  140. Name: name,
  141. Count: count,
  142. Metrics: zone.Metrics,
  143. })
  144. }
  145. sort.Sort(RatesByCount{rates})
  146. type statusData struct {
  147. Version string
  148. Zones Rates
  149. Uptime DayDuration
  150. Platform string
  151. Global struct {
  152. Queries metrics.Meter
  153. Histogram histogramData
  154. HistogramRecent histogramData
  155. }
  156. TopOption int
  157. }
  158. uptime := DayDuration{time.Since(hs.serverInfo.Started)}
  159. status := statusData{
  160. Version: VERSION,
  161. Zones: rates,
  162. Uptime: uptime,
  163. Platform: runtime.GOARCH + "-" + runtime.GOOS,
  164. TopOption: topOption,
  165. }
  166. status.Global.Queries = metrics.Get("queries").(*metrics.StandardMeter).Snapshot()
  167. setupHistogramData(metrics.Get("queries-histogram").(*metrics.StandardHistogram).Snapshot(), &status.Global.Histogram)
  168. statusTemplate, err := FSString(development, "/templates/status.html")
  169. if err != nil {
  170. log.Println("Could not read template:", err)
  171. w.WriteHeader(500)
  172. return
  173. }
  174. tmpl, err := template.New("status_html").Parse(statusTemplate)
  175. if err != nil {
  176. str := fmt.Sprintf("Could not parse template: %s", err)
  177. io.WriteString(w, str)
  178. return
  179. }
  180. err = tmpl.Execute(w, status)
  181. if err != nil {
  182. log.Println("Status template error", err)
  183. }
  184. }
  185. }
  186. func (hs *httpServer) mainServer(w http.ResponseWriter, req *http.Request) {
  187. if req.RequestURI != "/version" {
  188. http.NotFound(w, req)
  189. return
  190. }
  191. io.WriteString(w, `<html><head><title>GeoDNS `+
  192. hs.serverInfo.Version+`</title><body>`+
  193. `GeoDNS Server`+
  194. `</body></html>`)
  195. }
  196. type basicauth struct {
  197. h http.Handler
  198. }
  199. func (b *basicauth) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  200. // don't request passwords for the websocket interface (for now)
  201. // because 'wscat' doesn't support that.
  202. if r.RequestURI == "/monitor" {
  203. b.h.ServeHTTP(w, r)
  204. return
  205. }
  206. cfgMutex.RLock()
  207. user := Config.HTTP.User
  208. password := Config.HTTP.Password
  209. cfgMutex.RUnlock()
  210. if len(user) == 0 {
  211. b.h.ServeHTTP(w, r)
  212. return
  213. }
  214. ruser, rpass, ok := r.BasicAuth()
  215. if ok {
  216. if ruser == user && rpass == password {
  217. b.h.ServeHTTP(w, r)
  218. return
  219. }
  220. }
  221. w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, "GeoDNS Status"))
  222. http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
  223. return
  224. }