From 8b3204f96e1a47bcf2f2379dc43de7722c509605 Mon Sep 17 00:00:00 2001 From: AzenKain Date: Sun, 24 May 2026 15:47:39 +0700 Subject: [PATCH] feat: implement Goong API proxy with Redis caching, automatic key injection, and response filtering --- internal/controllers/goongController.go | 20 +++++- internal/services/goongService.go | 82 ++++++++++++++++++++++--- 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/internal/controllers/goongController.go b/internal/controllers/goongController.go index c0e7bab..e175fb7 100644 --- a/internal/controllers/goongController.go +++ b/internal/controllers/goongController.go @@ -86,7 +86,25 @@ func (ctrl *goongController) Proxy(c fiber.Ctx) error { c.Set("Vary", "Origin") c.Set("Cross-Origin-Resource-Policy", "cross-origin") - if c.Method() == "GET" { + if c.Method() == "GET" && statusCode == fiber.StatusOK { + isStaticAsset := strings.Contains(targetURL, "/tiles/") || + strings.Contains(targetURL, "/fonts/") || + strings.Contains(targetURL, "/glyphs/") || + strings.HasSuffix(targetURL, ".pbf") || + strings.HasSuffix(targetURL, ".png") || + strings.HasSuffix(targetURL, ".jpg") || + strings.HasSuffix(targetURL, ".webp") + + if !isStaticAsset { + c.Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0") + c.Set("CDN-Cache-Control", "no-store") + c.Set("Cloudflare-CDN-Cache-Control", "no-store") + } else { + c.Set("Cache-Control", "public, max-age=86400") + c.Set("CDN-Cache-Control", "max-age=86400") + c.Set("Cloudflare-CDN-Cache-Control", "max-age=86400") + } + } else if c.Method() == "GET" { c.Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0") c.Set("CDN-Cache-Control", "no-store") c.Set("Cloudflare-CDN-Cache-Control", "no-store") diff --git a/internal/services/goongService.go b/internal/services/goongService.go index 7abaf8a..4fbebf1 100644 --- a/internal/services/goongService.go +++ b/internal/services/goongService.go @@ -12,6 +12,8 @@ import ( "regexp" "strings" "time" + + "github.com/rs/zerolog/log" ) type GoongService interface { @@ -100,18 +102,82 @@ func (s *goongService) ProxyRequest(ctx context.Context, method string, targetUR if lowerK == "cookie" || lowerK == "authorization" || lowerK == "x-api-key" || lowerK == "x-csrf-token" { continue } + if strings.HasPrefix(lowerK, "sec-") || lowerK == "user-agent" || lowerK == "accept-language" || lowerK == "priority" || lowerK == "purpose" || lowerK == "dnt" { + continue + } req.Header.Set(k, v) } - resp, err := s.httpClient.Do(req) - if err != nil { - return 500, nil, nil, fmt.Errorf("failed to fetch from target: %w", err) - } - defer resp.Body.Close() + var resp *http.Response + var lastErr error + var respBody []byte - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return 500, nil, nil, fmt.Errorf("failed to read response body: %w", err) + for attempt := 1; attempt <= 3; attempt++ { + if ctx.Err() != nil { + lastErr = ctx.Err() + break + } + + attemptReq := req.Clone(ctx) + if len(reqBody) > 0 { + attemptReq.Body = io.NopCloser(bytes.NewReader(reqBody)) + } + + resp, err = s.httpClient.Do(attemptReq) + if err != nil { + lastErr = err + if ctx.Err() != nil { + break + } + time.Sleep(time.Duration(attempt*150) * time.Millisecond) + continue + } + + respBody, err = io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + lastErr = err + time.Sleep(time.Duration(attempt*150) * time.Millisecond) + continue + } + + if resp.StatusCode == http.StatusOK { + break + } + + if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 { + lastErr = fmt.Errorf("target returned status code %d: %s", resp.StatusCode, string(respBody)) + time.Sleep(time.Duration(attempt*150) * time.Millisecond) + continue + } + + break + } + + if resp == nil || (resp.StatusCode != http.StatusOK && lastErr != nil) { + statusCode := 500 + if resp != nil { + statusCode = resp.StatusCode + } + if ctx.Err() != nil { + return 499, nil, nil, ctx.Err() + } + log.Error(). + Err(lastErr). + Int("status_code", statusCode). + Str("url", finalURL). + Str("method", method). + Msg("Goong Map proxy request failed after retries") + return statusCode, nil, nil, fmt.Errorf("failed to fetch from target: %w", lastErr) + } + + if resp.StatusCode != http.StatusOK { + log.Warn(). + Int("status_code", resp.StatusCode). + Str("url", finalURL). + Str("method", method). + Str("resp_body", string(respBody)). + Msg("Goong Map proxy request returned non-200 status code") } respHeaders := make(map[string]string)