feat(caching): Implement cache hit detection and response header management

- Added functionality to mark responses as cache hits to prevent incorrect X-Cache headers.
- Introduced setCacheHitFlag function to traverse response writer wrappers and set cache hit flag.
- Updated cachingResponseWriter to manage cache hit state and adjust X-Cache header accordingly.
- Enhanced ProcessRequest and ProcessResponse methods to utilize new caching logic.

feat(extension): Introduce ResponseWriterWrapper and ResponseFinalizer interfaces

- Added ResponseWriterWrapper interface for extensions to wrap response writers.
- Introduced ResponseFinalizer interface for finalizing responses after processing.

refactor(manager): Improve response writer wrapping and finalization

- Updated Manager.Handler to wrap response writers through all enabled extensions.
- Implemented finalization of response writers after processing requests.

test(caching): Add comprehensive integration tests for caching behavior

- Created caching_test.go with tests for cache hit/miss, TTL expiration, pattern-based caching, and more.
- Ensured that caching logic works correctly for various scenarios including query strings and error responses.

test(routing): Add integration tests for routing behavior

- Created routing_test.go with tests for route priority, case sensitivity, default routes, and return directives.
- Verified that routing behaves as expected with multiple regex routes and named groups.
This commit is contained in:
Илья Глазунов
2025-12-12 01:03:32 +03:00
parent 81ac5c4d29
commit bd0b381195
6 changed files with 1275 additions and 20 deletions
+41 -2
View File
@@ -137,6 +137,10 @@ func (e *CachingExtension) ProcessRequest(ctx context.Context, w http.ResponseWr
e.hits++
e.mu.Unlock()
// Mark as cache hit to prevent setting X-Cache: MISS
// Try to find cachingResponseWriter in the wrapper chain
setCacheHitFlag(w)
for k, values := range entry.headers {
for _, v := range values {
w.Header().Add(k, v)
@@ -158,11 +162,36 @@ func (e *CachingExtension) ProcessRequest(ctx context.Context, w http.ResponseWr
return false, nil
}
// setCacheHitFlag tries to find cachingResponseWriter and set cache hit flag
func setCacheHitFlag(w http.ResponseWriter) {
// Direct match
if cw, ok := w.(*cachingResponseWriter); ok {
cw.SetCacheHit()
return
}
// Try unwrapping
type unwrapper interface {
Unwrap() http.ResponseWriter
}
for {
if u, ok := w.(unwrapper); ok {
w = u.Unwrap()
if cw, ok := w.(*cachingResponseWriter); ok {
cw.SetCacheHit()
return
}
} else {
return
}
}
}
// ProcessResponse caches the response if applicable
func (e *CachingExtension) ProcessResponse(ctx context.Context, w http.ResponseWriter, r *http.Request) {
// Response caching is handled by the CachingResponseWriter
// This is called after the response is written
w.Header().Set("X-Cache", "MISS")
// X-Cache header is set in the cachingResponseWriter.WriteHeader
}
// WrapResponseWriter wraps the response writer to capture the response for caching
@@ -186,16 +215,26 @@ type cachingResponseWriter struct {
buffer *bytes.Buffer
statusCode int
wroteHeader bool
cacheHit bool // Flag to indicate if this was a cache hit
}
func (cw *cachingResponseWriter) WriteHeader(code int) {
if !cw.wroteHeader {
cw.statusCode = code
cw.wroteHeader = true
// Set X-Cache: MISS header before writing headers (only if not a cache hit)
if !cw.cacheHit {
cw.ResponseWriter.Header().Set("X-Cache", "MISS")
}
cw.ResponseWriter.WriteHeader(code)
}
}
// SetCacheHit marks this response as a cache hit (to avoid setting X-Cache: MISS)
func (cw *cachingResponseWriter) SetCacheHit() {
cw.cacheHit = true
}
func (cw *cachingResponseWriter) Write(b []byte) (int, error) {
if !cw.wroteHeader {
cw.WriteHeader(http.StatusOK)