github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/aggregator/server/http/handlers.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package http
    22  
    23  import (
    24  	"bytes"
    25  	"encoding/json"
    26  	"errors"
    27  	"net/http"
    28  	"strings"
    29  
    30  	"github.com/m3db/m3/src/aggregator/aggregator"
    31  	xerrors "github.com/m3db/m3/src/x/errors"
    32  )
    33  
    34  // A list of HTTP endpoints.
    35  const (
    36  	HealthPath = "/health"
    37  	ResignPath = "/resign"
    38  	StatusPath = "/status"
    39  )
    40  
    41  var (
    42  	errRequestMustBeGet  = xerrors.NewInvalidParamsError(errors.New("request must be GET"))
    43  	errRequestMustBePost = xerrors.NewInvalidParamsError(errors.New("request must be POST"))
    44  )
    45  
    46  func registerHandlers(mux *http.ServeMux, aggregator aggregator.Aggregator) {
    47  	registerHealthHandler(mux)
    48  	registerResignHandler(mux, aggregator)
    49  	registerStatusHandler(mux, aggregator)
    50  }
    51  
    52  func registerHealthHandler(mux *http.ServeMux) {
    53  	mux.HandleFunc(HealthPath, func(w http.ResponseWriter, r *http.Request) {
    54  		w.Header().Set("Content-Type", "application/json")
    55  
    56  		if httpMethod := strings.ToUpper(r.Method); httpMethod != http.MethodGet {
    57  			writeErrorResponse(w, errRequestMustBeGet)
    58  			return
    59  		}
    60  		writeSuccessResponse(w)
    61  	})
    62  }
    63  
    64  func registerResignHandler(mux *http.ServeMux, aggregator aggregator.Aggregator) {
    65  	mux.HandleFunc(ResignPath, func(w http.ResponseWriter, r *http.Request) {
    66  		w.Header().Set("Content-Type", "application/json")
    67  
    68  		if httpMethod := strings.ToUpper(r.Method); httpMethod != http.MethodPost {
    69  			writeErrorResponse(w, errRequestMustBePost)
    70  			return
    71  		}
    72  
    73  		if err := aggregator.Resign(); err != nil {
    74  			writeErrorResponse(w, err)
    75  			return
    76  		}
    77  		writeSuccessResponse(w)
    78  	})
    79  }
    80  
    81  func registerStatusHandler(mux *http.ServeMux, aggregator aggregator.Aggregator) {
    82  	mux.HandleFunc(StatusPath, func(w http.ResponseWriter, r *http.Request) {
    83  		w.Header().Set("Content-Type", "application/json")
    84  
    85  		if httpMethod := strings.ToUpper(r.Method); httpMethod != http.MethodGet {
    86  			writeErrorResponse(w, errRequestMustBeGet)
    87  			return
    88  		}
    89  
    90  		status := aggregator.Status()
    91  		writeStatusResponse(w, status)
    92  	})
    93  }
    94  
    95  // Response is an HTTP response.
    96  type Response struct {
    97  	State string `json:"state,omitempty"`
    98  	Error string `json:"error,omitempty"`
    99  }
   100  
   101  // StatusResponse is a status response.
   102  type StatusResponse struct {
   103  	Response
   104  	Status aggregator.RuntimeStatus `json:"status,omitempty"`
   105  }
   106  
   107  // NewResponse creates a new empty response.
   108  func NewResponse() Response { return Response{} }
   109  
   110  // NewStatusResponse creates a new empty status response.
   111  func NewStatusResponse() StatusResponse { return StatusResponse{} }
   112  
   113  func newSuccessResponse() Response {
   114  	return Response{State: "OK"}
   115  }
   116  
   117  func newErrorResponse(err error) Response {
   118  	var errStr string
   119  	if err != nil {
   120  		errStr = err.Error()
   121  	}
   122  	return Response{State: "Error", Error: errStr}
   123  }
   124  
   125  func writeSuccessResponse(w http.ResponseWriter) {
   126  	response := newSuccessResponse()
   127  	writeResponse(w, response, nil)
   128  }
   129  
   130  func writeErrorResponse(w http.ResponseWriter, err error) {
   131  	writeResponse(w, nil, err)
   132  }
   133  
   134  func writeStatusResponse(w http.ResponseWriter, status aggregator.RuntimeStatus) {
   135  	response := NewStatusResponse()
   136  	response.Status = status
   137  	writeResponse(w, response, nil)
   138  }
   139  
   140  func writeResponse(w http.ResponseWriter, resp interface{}, err error) {
   141  	buf := bytes.NewBuffer(nil)
   142  	if encodeErr := json.NewEncoder(buf).Encode(&resp); encodeErr != nil {
   143  		w.WriteHeader(http.StatusInternalServerError)
   144  		resp = newErrorResponse(encodeErr)
   145  		json.NewEncoder(w).Encode(&resp)
   146  		return
   147  	}
   148  
   149  	if err == nil {
   150  		w.WriteHeader(http.StatusOK)
   151  	} else if xerrors.IsInvalidParams(err) {
   152  		w.WriteHeader(http.StatusBadRequest)
   153  	} else {
   154  		w.WriteHeader(http.StatusInternalServerError)
   155  	}
   156  	w.Write(buf.Bytes())
   157  }