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) }