feat: implement Goong API proxy with Redis caching, automatic key injection, and response filtering
All checks were successful
Build and Release / release (push) Successful in 1m31s

This commit is contained in:
2026-05-24 15:47:39 +07:00
parent 5a75798609
commit 8b3204f96e
2 changed files with 93 additions and 9 deletions

View File

@@ -86,7 +86,25 @@ func (ctrl *goongController) Proxy(c fiber.Ctx) error {
c.Set("Vary", "Origin") c.Set("Vary", "Origin")
c.Set("Cross-Origin-Resource-Policy", "cross-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("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
c.Set("CDN-Cache-Control", "no-store") c.Set("CDN-Cache-Control", "no-store")
c.Set("Cloudflare-CDN-Cache-Control", "no-store") c.Set("Cloudflare-CDN-Cache-Control", "no-store")

View File

@@ -12,6 +12,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/rs/zerolog/log"
) )
type GoongService interface { 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" { if lowerK == "cookie" || lowerK == "authorization" || lowerK == "x-api-key" || lowerK == "x-csrf-token" {
continue continue
} }
if strings.HasPrefix(lowerK, "sec-") || lowerK == "user-agent" || lowerK == "accept-language" || lowerK == "priority" || lowerK == "purpose" || lowerK == "dnt" {
continue
}
req.Header.Set(k, v) req.Header.Set(k, v)
} }
resp, err := s.httpClient.Do(req) var resp *http.Response
if err != nil { var lastErr error
return 500, nil, nil, fmt.Errorf("failed to fetch from target: %w", err) var respBody []byte
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) for attempt := 1; attempt <= 3; attempt++ {
if err != nil { if ctx.Err() != nil {
return 500, nil, nil, fmt.Errorf("failed to read response body: %w", err) 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) respHeaders := make(map[string]string)