2020-11-28 23:34:20 +05:00
|
|
|
package httpserver
|
|
|
|
|
|
|
|
import (
|
2020-12-23 13:27:17 +05:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"log"
|
2020-11-28 23:34:20 +05:00
|
|
|
"net/http"
|
2020-12-23 13:27:17 +05:00
|
|
|
"strconv"
|
2020-11-29 06:09:35 +05:00
|
|
|
"strings"
|
2020-12-23 13:27:17 +05:00
|
|
|
"time"
|
2020-11-28 23:34:20 +05:00
|
|
|
|
|
|
|
"go.dev.pztrn.name/metricator/internal/common"
|
2020-12-23 13:27:17 +05:00
|
|
|
"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}
|
2020-11-28 23:34:20 +05:00
|
|
|
)
|
|
|
|
|
|
|
|
// HTTP requests handler.
|
|
|
|
type handler struct {
|
2020-11-29 06:09:35 +05:00
|
|
|
handlers map[string]common.HTTPHandlerFunc
|
2020-11-28 23:34:20 +05:00
|
|
|
}
|
|
|
|
|
2020-12-23 13:27:17 +05:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-11-28 23:34:20 +05:00
|
|
|
// Registers request's handler.
|
2020-12-23 13:27:17 +05:00
|
|
|
func (h *handler) register(appName string, hndl common.HTTPHandlerFunc) {
|
|
|
|
h.handlers[appName] = hndl
|
2020-11-28 23:34:20 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
// ServeHTTP handles every HTTP request.
|
2020-11-29 06:09:35 +05:00
|
|
|
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2020-12-23 13:27:17 +05:00
|
|
|
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 {
|
2020-11-29 06:09:35 +05:00
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
2020-12-23 13:27:17 +05:00
|
|
|
_, _ = w.Write([]byte("400 bad request - " + err.Error()))
|
2020-11-29 06:09:35 +05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-12-23 13:27:17 +05:00
|
|
|
// 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()))
|
2020-11-29 06:09:35 +05:00
|
|
|
|
2020-12-23 13:27:17 +05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
_, _ = w.Write(appsList)
|
2020-11-29 06:09:35 +05:00
|
|
|
|
|
|
|
return
|
2020-12-23 13:27:17 +05:00
|
|
|
case "metrics":
|
|
|
|
handler, found := h.handlers[rInfo.Application]
|
|
|
|
if !found {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
_, _ = w.Write([]byte("400 bad request - " + errInvalidApplication.Error()))
|
2020-11-29 06:09:35 +05:00
|
|
|
|
2020-12-23 13:27:17 +05:00
|
|
|
return
|
|
|
|
}
|
2020-11-29 06:09:35 +05:00
|
|
|
|
2020-12-23 13:27:17 +05:00
|
|
|
// 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
|
|
|
|
}
|
2020-11-29 06:09:35 +05:00
|
|
|
|
2020-12-23 13:27:17 +05:00
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
_, _ = w.Write([]byte("400 bad request - " + errInvalidPath.Error()))
|
2020-11-29 06:09:35 +05:00
|
|
|
}
|