http.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package main
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "log"
  8. "net/http"
  9. "strconv"
  10. "time"
  11. "github.com/abh/geodns/v3/appconfig"
  12. "github.com/abh/geodns/v3/monitor"
  13. "github.com/abh/geodns/v3/zones"
  14. "github.com/prometheus/client_golang/prometheus/promhttp"
  15. "golang.org/x/sync/errgroup"
  16. )
  17. type httpServer struct {
  18. mux *http.ServeMux
  19. zones *zones.MuxManager
  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. func topParam(req *http.Request, def int) int {
  40. req.ParseForm()
  41. topOption := def
  42. topParam := req.Form["top"]
  43. if len(topParam) > 0 {
  44. var err error
  45. topOption, err = strconv.Atoi(topParam[0])
  46. if err != nil {
  47. topOption = def
  48. }
  49. }
  50. return topOption
  51. }
  52. func NewHTTPServer(mm *zones.MuxManager, serverInfo *monitor.ServerInfo) *httpServer {
  53. hs := &httpServer{
  54. zones: mm,
  55. mux: &http.ServeMux{},
  56. serverInfo: serverInfo,
  57. }
  58. hs.mux.HandleFunc("/", hs.mainServer)
  59. hs.mux.Handle("/metrics", promhttp.Handler())
  60. return hs
  61. }
  62. func (hs *httpServer) Mux() *http.ServeMux {
  63. return hs.mux
  64. }
  65. func (hs *httpServer) Run(ctx context.Context, listen string) error {
  66. log.Println("Starting HTTP interface on", listen)
  67. srv := http.Server{
  68. Addr: listen,
  69. Handler: &basicauth{h: hs.mux},
  70. ReadTimeout: 5 * time.Second,
  71. IdleTimeout: 10 * time.Second,
  72. WriteTimeout: 10 * time.Second,
  73. }
  74. g, ctx := errgroup.WithContext(ctx)
  75. g.Go(func() error {
  76. err := srv.ListenAndServe()
  77. if err != nil {
  78. if !errors.Is(err, http.ErrServerClosed) {
  79. return err
  80. }
  81. }
  82. return nil
  83. })
  84. g.Go(func() error {
  85. <-ctx.Done()
  86. log.Printf("shutting down http server")
  87. return srv.Shutdown(ctx)
  88. })
  89. return g.Wait()
  90. }
  91. func (hs *httpServer) mainServer(w http.ResponseWriter, req *http.Request) {
  92. if req.RequestURI != "/version" {
  93. http.NotFound(w, req)
  94. return
  95. }
  96. w.Header().Set("Content-Type", "text/plain")
  97. w.WriteHeader(200)
  98. io.WriteString(w, `GeoDNS `+hs.serverInfo.Version+`\n`)
  99. }
  100. type basicauth struct {
  101. h http.Handler
  102. }
  103. func (b *basicauth) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  104. // cfgMutex.RLock()
  105. user := appconfig.Config.HTTP.User
  106. password := appconfig.Config.HTTP.Password
  107. // cfgMutex.RUnlock()
  108. if len(user) == 0 {
  109. b.h.ServeHTTP(w, r)
  110. return
  111. }
  112. ruser, rpass, ok := r.BasicAuth()
  113. if ok {
  114. if ruser == user && rpass == password {
  115. b.h.ServeHTTP(w, r)
  116. return
  117. }
  118. }
  119. w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, "GeoDNS Status"))
  120. http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
  121. }