-
Notifications
You must be signed in to change notification settings - Fork 47
Description
Summary
Static GTFS endpoints (agencies, routes, stops, shapes) use Cache-Control: public, max-age=300 but have no conditional request support. Once max-age expires, clients are forced to re-download the full JSON payload even if the underlying GTFS dataset has not changed in days. This causes unnecessary bandwidth consumption and redundant JSON serialization on every cache expiry cycle.
While reviewing this, I also found a bug in caching_middleware.go that would silently break any ETag implementation added in the future, so both need to be addressed together.
1. ETag Support
A SHA-256 FileHash is already computed and stored on every GTFS import in gtfsdb/helpers.go — this can be used directly as the ETag value with no extra runtime hashing cost at all.
- Proposed change: Expose
FileHashon theManagerstruct after a successful load ininternal/gtfs/static.go, then add a new middleware for static endpoints that reads the incomingIf-None-Matchheader. - If it matches the current hash, return
304 Not Modifiedwith an empty body immediately, skipping all database queries and JSON serialization. If it does not match, proceed normally and writeETag: "<hash>"on the response. - Scope: This must be scoped to static tier endpoints only. Applying it to real-time endpoints like
arrivals-and-departures-for-stoporvehicles-for-agencywould incorrectly serve stale vehicle positions as unchanged, which is a correctness bug.
2. Middleware Bug
cacheControlWriter.WriteHeader in caching_middleware.go currently treats 304 as a non-2xx response and overwrites its Cache-Control header with no-cache, no-store, must-revalidate. This causes clients to discard their cached copy on every 304 response, completely defeating the purpose of conditional requests. The existing tests in caching_middleware_test.go only cover 200 and 404 responses so this has gone undetected.
// current — 304 incorrectly hits the else branch
if code >= 200 && code < 300 {
w.Header().Set("Cache-Control", w.headerValue)
} else {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
}
// fix
if (code >= 200 && code < 300) || code == http.StatusNotModified {
w.Header().Set("Cache-Control", w.headerValue)
}