openreplay/ee/backend/pkg/server/tracer/middleware.go
Alexander 6830c8879f
web module refactoring (#2725)
* feat(server): moved an http server object into a pkg subdir to be reusable for http, spots, and integrations

* feat(web): isolated web module (server, router, middleware, utils) used in spots and new integrations

* feat(web): removed possible panic

* feat(web): split all handlers from http service into different packages for better management.

* feat(web): changed router's method signature

* feat(web): added missing handlers interface

* feat(web): added health middleware to remove unnecessary checks

* feat(web): customizable middleware set for web servers

* feat(web): simplified the handler's structure

* feat(web): created an unified server.Run method for all web services (http, spot, integrations)

* feat(web): fixed a json size limit issue

* feat(web): removed Keys and PG connection from router

* feat(web): simplified integration's main file

* feat(web): simplified spot's main file

* feat(web): simplified http's main file (builder)

* feat(web): refactored audit trail functionality

* feat(web): added ee version of audit trail

* feat(web): added ee version of conditions module

* feat(web): moved ee version of some web session structs

* feat(web): new format of web metrics

* feat(web): added new web metrics to all handlers

* feat(web): added justExpired feature to web ingest handler

* feat(web): added small integrations improvements
2024-11-21 17:48:04 +01:00

96 lines
2.7 KiB
Go

package tracer
import (
"bytes"
"encoding/json"
"io"
"net/http"
"github.com/gorilla/mux"
"openreplay/backend/pkg/server/user"
)
type statusWriter struct {
http.ResponseWriter
statusCode int
}
func (w *statusWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode
w.ResponseWriter.WriteHeader(statusCode)
}
func (w *statusWriter) Write(b []byte) (int, error) {
if w.statusCode == 0 {
w.statusCode = http.StatusOK
}
return w.ResponseWriter.Write(b)
}
func (t *tracerImpl) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Read body and restore the io.ReadCloser to its original state
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "can't read body", http.StatusBadRequest)
return
}
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// Use custom response writer to get the status code
sw := &statusWriter{ResponseWriter: w}
// Serve the request
next.ServeHTTP(sw, r)
t.logRequest(r, bodyBytes, sw.statusCode)
})
}
var routeMatch = map[string]string{
"POST" + "/spot/v1/spots": "createSpot",
"GET" + "/spot/v1/spots/{id}": "getSpot",
"PATCH" + "/spot/v1/spots/{id}": "updateSpot",
"GET" + "/spot/v1/spots": "getSpots",
"DELETE" + "/spot/v1/spots": "deleteSpots",
"POST" + "/spot/v1/spots/{id}/comment": "addComment",
"GET" + "/spot/v1/spots/{id}/video": "getSpotVideo",
"PATCH" + "/spot/v1/spots/{id}/public-key": "updatePublicKey",
}
func (t *tracerImpl) logRequest(r *http.Request, bodyBytes []byte, statusCode int) {
pathTemplate, err := mux.CurrentRoute(r).GetPathTemplate()
if err != nil {
t.log.Error(r.Context(), "failed to get path template: %s", err)
}
t.log.Debug(r.Context(), "path template: %s", pathTemplate)
if _, ok := routeMatch[r.Method+pathTemplate]; !ok {
t.log.Debug(r.Context(), "no match for route: %s %s", r.Method, pathTemplate)
return
}
// Convert the parameters to json
query := r.URL.Query()
params := make(map[string]interface{})
for key, values := range query {
if len(values) > 1 {
params[key] = values
} else {
params[key] = values[0]
}
}
jsonData, err := json.Marshal(params)
if err != nil {
t.log.Error(r.Context(), "failed to marshal query parameters: %s", err)
}
requestData := &RequestData{
Action: routeMatch[r.Method+pathTemplate],
Method: r.Method,
PathFormat: pathTemplate,
Endpoint: r.URL.Path,
Payload: bodyBytes,
Parameters: jsonData,
Status: statusCode,
}
userData := r.Context().Value("userData").(*user.User)
t.trace(userData, requestData)
// DEBUG
t.log.Debug(r.Context(), "request data: %v", requestData)
}