diff options
author | Martin Polden <mpolden@mpolden.no> | 2020-06-19 21:13:06 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2020-06-19 21:13:06 +0200 |
commit | a197f906c275a35db82b1f1b634337cc9dc0c273 (patch) | |
tree | 7e57f76c6cc792c6cd56d429400e634e06ad2999 | |
parent | b5b4353ad8b1c5535fe592dc6bed03853ba73ccf (diff) |
http: Support non-JSON handlers
-rw-r--r-- | http/http.go | 61 | ||||
-rw-r--r-- | http/router.go | 31 |
2 files changed, 58 insertions, 34 deletions
diff --git a/http/http.go b/http/http.go index 8a219dc..d547010 100644 --- a/http/http.go +++ b/http/http.go @@ -2,6 +2,7 @@ package http import ( "context" + "encoding/json" "fmt" "log" "net" @@ -15,6 +16,10 @@ import ( "github.com/mpolden/zdns/sql" ) +const ( + jsonMediaType = "application/json" +) + // A Server defines paramaters for running an HTTP server. The HTTP server serves an API for inspecting cache contents // and request log. type Server struct { @@ -130,10 +135,22 @@ func resolutionFrom(r *http.Request) (time.Duration, error) { return time.ParseDuration(param) } -func (s *Server) cacheHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { +func writeJSONHeader(w http.ResponseWriter) { w.Header().Set("Content-Type", jsonMediaType) } + +func writeJSON(w http.ResponseWriter, data interface{}) { + b, err := json.Marshal(data) + if err != nil { + panic(err) + } + writeJSONHeader(w) + w.Write(b) +} + +func (s *Server) cacheHandler(w http.ResponseWriter, r *http.Request) *httpError { count, err := countFrom(r) if err != nil { - return nil, newHTTPBadRequest(err) + writeJSONHeader(w) + return newHTTPBadRequest(err) } cacheValues := s.cache.List(count) entries := make([]entry, 0, len(cacheValues)) @@ -147,24 +164,28 @@ func (s *Server) cacheHandler(w http.ResponseWriter, r *http.Request) (interface Rcode: dnsutil.RcodeToString[v.Rcode()], }) } - return entries, nil + writeJSON(w, entries) + return nil } -func (s *Server) cacheResetHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { +func (s *Server) cacheResetHandler(w http.ResponseWriter, r *http.Request) *httpError { s.cache.Reset() - return struct { + writeJSON(w, struct { Message string `json:"message"` - }{"Cleared cache."}, nil + }{"Cleared cache."}) + return nil } -func (s *Server) logHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { +func (s *Server) logHandler(w http.ResponseWriter, r *http.Request) *httpError { count, err := countFrom(r) if err != nil { - return nil, newHTTPBadRequest(err) + writeJSONHeader(w) + return newHTTPBadRequest(err) } logEntries, err := s.logger.Read(count) if err != nil { - return nil, newHTTPError(err) + writeJSONHeader(w) + return newHTTPError(err) } entries := make([]entry, 0, len(logEntries)) for _, le := range logEntries { @@ -178,17 +199,20 @@ func (s *Server) logHandler(w http.ResponseWriter, r *http.Request) (interface{} Answers: le.Answers, }) } - return entries, nil + writeJSON(w, entries) + return nil } -func (s *Server) basicMetricHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { +func (s *Server) basicMetricHandler(w http.ResponseWriter, r *http.Request) *httpError { resolution, err := resolutionFrom(r) if err != nil { - return nil, newHTTPBadRequest(err) + writeJSONHeader(w) + return newHTTPBadRequest(err) } lstats, err := s.logger.Stats(resolution) if err != nil { - return nil, newHTTPError(err) + writeJSONHeader(w) + return newHTTPError(err) } requests := make([]request, 0, len(lstats.Events)) for _, e := range lstats.Events { @@ -202,7 +226,7 @@ func (s *Server) basicMetricHandler(w http.ResponseWriter, r *http.Request) (int if s.sqlCache != nil { bstats = &backendStats{PendingTasks: s.sqlCache.Stats().PendingTasks} } - return stats{ + stats := stats{ Summary: summary{ Log: logStats{ Since: lstats.Since.Format(time.RFC3339), @@ -217,10 +241,12 @@ func (s *Server) basicMetricHandler(w http.ResponseWriter, r *http.Request) (int }, }, Requests: requests, - }, nil + } + writeJSON(w, stats) + return nil } -func (s *Server) metricHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { +func (s *Server) metricHandler(w http.ResponseWriter, r *http.Request) *httpError { format := "" if formatParams := r.URL.Query()["format"]; len(formatParams) > 0 { format = formatParams[0] @@ -229,7 +255,8 @@ func (s *Server) metricHandler(w http.ResponseWriter, r *http.Request) (interfac case "", "basic": return s.basicMetricHandler(w, r) } - return nil, newHTTPBadRequest(fmt.Errorf("invalid metric format: %s", format)) + writeJSONHeader(w) + return newHTTPBadRequest(fmt.Errorf("invalid metric format: %s", format)) } // Close shuts down the HTTP server. diff --git a/http/router.go b/http/router.go index 27adc33..2e6b77b 100644 --- a/http/router.go +++ b/http/router.go @@ -15,32 +15,29 @@ type route struct { handler appHandler } -type appHandler func(http.ResponseWriter, *http.Request) (interface{}, *httpError) +type appHandler func(http.ResponseWriter, *http.Request) *httpError func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - data, e := fn(w, r) - w.Header().Set("Content-Type", "application/json") - if e != nil { // e is *Error, not os.Error. + if e := fn(w, r); e != nil { // e is *httpError, not os.Error. if e.Message == "" { e.Message = e.err.Error() } - out, err := json.Marshal(e) - if err != nil { - panic(err) - } w.WriteHeader(e.Status) - w.Write(out) - } else if data != nil { - out, err := json.Marshal(data) - if err != nil { - panic(err) + if w.Header().Get("Content-Type") == jsonMediaType { + out, err := json.Marshal(e) + if err != nil { + panic(err) + } + w.Write(out) + } else { + w.Write([]byte(e.Message)) } - w.Write(out) } } -func notFoundHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { - return nil, &httpError{ +func notFoundHandler(w http.ResponseWriter, r *http.Request) *httpError { + writeJSONHeader(w) + return &httpError{ Status: http.StatusNotFound, Message: "Resource not found", } @@ -57,7 +54,7 @@ func (r *router) route(method, path string, handler appHandler) *route { } func (r *router) handler() http.Handler { - return appHandler(func(w http.ResponseWriter, req *http.Request) (interface{}, *httpError) { + return appHandler(func(w http.ResponseWriter, req *http.Request) *httpError { for _, route := range r.routes { if route.match(req) { return route.handler(w, req) |