| package goscaffold |
| |
| import ( |
| "encoding/json" |
| "errors" |
| "net/http" |
| "net/http/pprof" |
| ) |
| |
| /* |
| requestHandler handles all requests and stops them if we are marked down. |
| */ |
| type requestHandler struct { |
| s *HTTPScaffold |
| child http.Handler |
| } |
| |
| func (h *requestHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) { |
| startErr := h.s.tracker.start() |
| if startErr == nil { |
| h.child.ServeHTTP(resp, req) |
| h.s.tracker.end() |
| } else { |
| writeUnavailable(resp, req, NotReady, startErr) |
| } |
| } |
| |
| /* |
| managementHandler adds support for health checks and diagnostics. |
| */ |
| type managementHandler struct { |
| s *HTTPScaffold |
| mux *http.ServeMux |
| child http.Handler |
| } |
| |
| func (h *managementHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) { |
| handler, pattern := h.mux.Handler(req) |
| if pattern == "" && h.child != nil { |
| // Fall through for stuff that's not a management call |
| h.child.ServeHTTP(resp, req) |
| } else { |
| // Handler may be one of ours, or a built-in not found handler |
| handler.ServeHTTP(resp, req) |
| } |
| } |
| |
| func (s *HTTPScaffold) createManagementHandler() *managementHandler { |
| h := &managementHandler{ |
| s: s, |
| mux: http.NewServeMux(), |
| } |
| |
| // Manually register paths from "pprof" package because we are |
| // not using a standard HTTP handler here. |
| h.mux.HandleFunc("/debug/pprof/", pprof.Index) |
| h.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) |
| h.mux.HandleFunc("/debug/pprof/profile", pprof.Profile) |
| h.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) |
| h.mux.HandleFunc("/debug/pprof/trace", pprof.Trace) |
| |
| if s.healthPath != "" { |
| h.mux.HandleFunc(s.healthPath, s.handleHealth) |
| } |
| if s.readyPath != "" { |
| h.mux.HandleFunc(s.readyPath, s.handleReady) |
| } |
| if s.markdownPath != "" { |
| h.mux.HandleFunc(s.markdownPath, s.handleMarkdown) |
| } |
| return h |
| } |
| |
| func (s *HTTPScaffold) callHealthCheck() (HealthStatus, error) { |
| if s.healthCheck == nil { |
| return OK, nil |
| } |
| status, err := s.healthCheck() |
| if status == OK { |
| return OK, nil |
| } |
| if err == nil { |
| return status, errors.New(status.String()) |
| } |
| return status, err |
| } |
| |
| /* |
| handleHealth only fails if the user's health check function tells us. |
| */ |
| func (s *HTTPScaffold) handleHealth(resp http.ResponseWriter, req *http.Request) { |
| if req.Method != "GET" { |
| resp.WriteHeader(http.StatusMethodNotAllowed) |
| return |
| } |
| |
| status, healthErr := s.callHealthCheck() |
| |
| if status == Failed { |
| writeUnavailable(resp, req, status, healthErr) |
| } else { |
| resp.WriteHeader(http.StatusOK) |
| } |
| } |
| |
| /* |
| handleReady fails if we are marked down and also if the user's health function |
| tells us. |
| */ |
| func (s *HTTPScaffold) handleReady(resp http.ResponseWriter, req *http.Request) { |
| if req.Method != "GET" { |
| resp.WriteHeader(http.StatusMethodNotAllowed) |
| return |
| } |
| |
| status, healthErr := s.callHealthCheck() |
| if status == OK { |
| healthErr = s.tracker.markedDown() |
| if healthErr != nil { |
| status = NotReady |
| } |
| } |
| |
| if status == OK { |
| resp.WriteHeader(http.StatusOK) |
| } else { |
| writeUnavailable(resp, req, status, healthErr) |
| } |
| } |
| |
| /* |
| handleMarkdown handles a request to mark down the server. |
| */ |
| func (s *HTTPScaffold) handleMarkdown(resp http.ResponseWriter, req *http.Request) { |
| if req.Method != s.markdownMethod { |
| resp.WriteHeader(http.StatusMethodNotAllowed) |
| return |
| } |
| |
| req.Body.Close() |
| s.tracker.markDown() |
| if s.markdownHandler != nil { |
| s.markdownHandler() |
| } |
| } |
| |
| func writeUnavailable( |
| resp http.ResponseWriter, req *http.Request, |
| stat HealthStatus, err error) { |
| |
| mt := SelectMediaType(req, []string{"text/plain", "application/json"}) |
| |
| resp.WriteHeader(http.StatusServiceUnavailable) |
| switch mt { |
| case "application/json": |
| re := map[string]string{ |
| "status": stat.String(), |
| "reason": err.Error(), |
| } |
| buf, _ := json.Marshal(&re) |
| resp.Header().Set("Content-Type", mt) |
| resp.Write(buf) |
| default: |
| resp.Header().Set("Content-Type", "text/plain") |
| resp.Write([]byte(err.Error())) |
| } |
| } |