Stanislav N. aka pztrn
529113a41a
Metrics data now stored as structures. This is a first step for autodiscovery helping for NMSes. HTTP requests now logged. Got rid of context.Context for getting metric data in applications because context.Context is useless here.
163 lines
3.8 KiB
Go
163 lines
3.8 KiB
Go
package httpserver
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.dev.pztrn.name/metricator/internal/common"
|
|
"go.dev.pztrn.name/metricator/internal/models"
|
|
)
|
|
|
|
var (
|
|
errInvalidAPIVersion = errors.New("invalid API version")
|
|
errInvalidApplication = errors.New("invalid application")
|
|
errInvalidPath = errors.New("invalid path")
|
|
errNoAppsRegistered = errors.New("no applications registered")
|
|
errNoData = errors.New("no data")
|
|
|
|
supportedAPIVersions = []int{1}
|
|
)
|
|
|
|
// HTTP requests handler.
|
|
type handler struct {
|
|
handlers map[string]common.HTTPHandlerFunc
|
|
}
|
|
|
|
// Gets applications list from handlers map.
|
|
func (h *handler) getAppsList() ([]byte, error) {
|
|
apps := make([]string, 0, len(h.handlers))
|
|
|
|
for appName := range h.handlers {
|
|
apps = append(apps, appName)
|
|
}
|
|
|
|
appsList, err := json.Marshal(apps)
|
|
if err != nil {
|
|
// ToDo: log error
|
|
|
|
return nil, errNoAppsRegistered
|
|
}
|
|
|
|
return appsList, nil
|
|
}
|
|
|
|
// Gets request information from URL. Returns a structure with filled request
|
|
// info and error if it occurs.
|
|
func (h *handler) getRequestInfo(r *http.Request) (*models.RequestInfo, error) {
|
|
// Request isn't for API or isn't versioned.
|
|
if !strings.HasPrefix(r.URL.Path, "/api/v") {
|
|
return nil, errInvalidPath
|
|
}
|
|
|
|
// Note: first element will always be empty!
|
|
pathSplitted := strings.Split(r.URL.Path, "/")
|
|
|
|
// Request is for API but not enough items in URL was passed.
|
|
if len(pathSplitted) < 4 {
|
|
return nil, errInvalidPath
|
|
}
|
|
|
|
var (
|
|
appName string
|
|
metricName string
|
|
requestType string
|
|
)
|
|
|
|
// Parse API version.
|
|
apiVersionRaw := strings.TrimLeft(pathSplitted[2], "v")
|
|
apiVersion, err := strconv.Atoi(apiVersionRaw)
|
|
if err != nil {
|
|
// ToDo: log error
|
|
return nil, errInvalidAPIVersion
|
|
}
|
|
|
|
// Get request type and key.
|
|
requestType = pathSplitted[3]
|
|
if len(pathSplitted) >= 5 {
|
|
appName = pathSplitted[4]
|
|
}
|
|
if len(pathSplitted) >= 6 {
|
|
metricName = strings.Join(pathSplitted[5:], "/")
|
|
}
|
|
|
|
reqInfo := &models.RequestInfo{
|
|
ApiVersion: apiVersion,
|
|
Application: appName,
|
|
Metric: metricName,
|
|
RequestType: requestType,
|
|
}
|
|
|
|
return reqInfo, nil
|
|
}
|
|
|
|
// Registers request's handler.
|
|
func (h *handler) register(appName string, hndl common.HTTPHandlerFunc) {
|
|
h.handlers[appName] = hndl
|
|
}
|
|
|
|
// ServeHTTP handles every HTTP request.
|
|
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
startTime := time.Now()
|
|
defer func() {
|
|
requestDuration := time.Now().Sub(startTime)
|
|
|
|
log.Printf("[HTTP Request] from %s to %s, duration %.4fs\n", r.RemoteAddr, r.URL.Path, requestDuration.Seconds())
|
|
}()
|
|
|
|
// Validate request and extract needed info.
|
|
rInfo, err := h.getRequestInfo(r)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_, _ = w.Write([]byte("400 bad request - " + err.Error()))
|
|
|
|
return
|
|
}
|
|
|
|
// Process request type. Here we process only known requests types,
|
|
// by default request should go to specific application's handler.
|
|
switch rInfo.RequestType {
|
|
// ToDo: move to constants.
|
|
case "apps_list":
|
|
appsList, err := h.getAppsList()
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_, _ = w.Write([]byte("400 bad request - " + err.Error()))
|
|
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write(appsList)
|
|
|
|
return
|
|
case "metrics":
|
|
handler, found := h.handlers[rInfo.Application]
|
|
if !found {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_, _ = w.Write([]byte("400 bad request - " + errInvalidApplication.Error()))
|
|
|
|
return
|
|
}
|
|
|
|
// Get data from handler.
|
|
data := handler(rInfo)
|
|
if data == "" {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_, _ = w.Write([]byte("400 bad request - " + errNoData.Error()))
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(data))
|
|
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_, _ = w.Write([]byte("400 bad request - " + errInvalidPath.Error()))
|
|
}
|