Metrics data is stored as structure, HTTP requests logging, got rid of context.
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.
This commit is contained in:
@@ -1,11 +1,26 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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.
|
||||
@@ -13,41 +28,135 @@ 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(name string, hndl common.HTTPHandlerFunc) {
|
||||
h.handlers[name] = hndl
|
||||
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) {
|
||||
if !strings.HasPrefix(r.URL.Path, "/api") {
|
||||
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 - invalid path"))
|
||||
_, _ = w.Write([]byte("400 bad request - " + err.Error()))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Request validation.
|
||||
pathSplitted := strings.Split(r.URL.Path, "/")
|
||||
if len(pathSplitted) < 3 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte("400 Bad Request - invalid path"))
|
||||
}
|
||||
// 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()))
|
||||
|
||||
handler, found := h.handlers[pathSplitted[2]]
|
||||
if !found {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte("400 Bad Request - invalid application name"))
|
||||
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
|
||||
}
|
||||
|
||||
requestContext := r.Context()
|
||||
|
||||
// Compose metric name.
|
||||
metricName := strings.Join(pathSplitted[3:], "/")
|
||||
ctx := context.WithValue(requestContext, common.ContextKeyMetric, metricName)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(handler(ctx)))
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte("400 bad request - " + errInvalidPath.Error()))
|
||||
}
|
||||
|
Reference in New Issue
Block a user