2025-11-08 16:17:55 +01:00

99 lines
3.1 KiB
Go

package api
import (
"encoding/json"
"log"
"mime"
"net/http"
"net/url"
"strings"
)
// isJSONLike checks if a string looks like it might be JSON
func isJSONLike(s string) bool {
s = strings.TrimSpace(s)
return (strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}")) ||
(strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]"))
}
// RequestEcho represents the structure of our response
type RequestEcho struct {
URL string `json:"url"`
Method string `json:"method"`
Path string `json:"path"`
QueryParams url.Values `json:"query_params"`
Headers http.Header `json:"headers"` // Use http.Header for canonical keys
Body interface{} `json:"body"` // Can be structured JSON or raw string
BodyRaw string `json:"body_raw"` // Original body as string
RemoteAddr string `json:"remote_addr"`
ContentType string `json:"content_type"`
ContentLength int64 `json:"content_length"`
}
func EchoHandler() http.Handler
{
// Read body
bodyBytes := c.Body() // Fiber's way to get the body
bodyString := string(bodyBytes)
// Try to parse body as JSON
var parsedBody interface{}
contentTypeHeader := c.Get("Content-Type") // Get Content-Type header
if len(bodyBytes) > 0 {
mediaType, _, err := mime.ParseMediaType(contentTypeHeader)
// Only attempt JSON parsing if Content-Type is application/json or it looks like JSON
if (err == nil && mediaType == "application/json") || isJSONLike(bodyString) {
if jsonErr := json.Unmarshal(bodyBytes, &parsedBody); jsonErr != nil {
// If JSON parsing fails, parsedBody remains nil.
// It will be set to bodyString later if it's still nil.
parsedBody = nil
}
}
}
if parsedBody == nil && len(bodyBytes) > 0 {
// For non-JSON bodies or if JSON parsing failed, use the raw string
parsedBody = bodyString
}
// Query Parameters
queryParams, _ := url.ParseQuery(string(c.Request().URI().QueryString()))
// Headers - ensuring canonical keys like net/http.Header
headers := make(http.Header)
c.Context().Request.Header.VisitAll(func(key, value []byte) {
k := string(key)
v := string(value)
headers.Add(k, v) // http.Header.Add appends, and canonicalizes the key on first Set/Add
})
// Create our response structure
echo := RequestEcho{
URL: c.OriginalURL(), // Path and query string
Method: c.Method(),
Path: c.Path(),
QueryParams: queryParams,
Headers: headers,
Body: parsedBody,
BodyRaw: bodyString,
RemoteAddr: c.Context().RemoteAddr().String(), // IP and Port
ContentType: contentTypeHeader,
ContentLength: int64(c.Context().Request.Header.ContentLength()),
}
// Marshal to JSON and write response
jsonResponse, err := json.MarshalIndent(echo, "", " ")
if err != nil {
log.Printf("Error creating JSON response: %v", err)
return c.Status(http.StatusInternalServerError).SendString("Error creating JSON response")
}
c.Set("Content-Type", "application/json")
c.Status(http.StatusOK)
// Log the request to stdout
// fmt.Printf("Received %s request to %s\n", c.Method(), c.Path())
return c.Send(jsonResponse)
}