Update Apr 8 2009: Apparently this bug existed in previous Safari versions (at least back to 3.1), i.e. including “must-revalidate” in Cache-Control means Expire and max-age will both be ignored by Safari. Here’s the Apple bug, if you happen to be an employee. I created an Apple account but still couldn’t find it.
Short version: Safari 4 beta incorrectly interprets the Cache-Control “must-revalidate” directive and re-requests the file each time despite freshness info sent via “max-age”.
Long version
When a server sends Cache-Control with “must-revalidate”, this tells clients/caches that, after it expires, the cached resource should not be used without first checking with the server (sending a conditional GET). From the spec (my emphasis):
When the must-revalidate directive is present in a response received by a cache, that cache MUST NOT use the entry after it becomes stale to respond to a subsequent request without first revalidating it with the origin server. [HTTP/1.1]
In other words, while the cache is fresh, there’s no need to revalidate.
E.g. I serve this Javascript file with the header “Cache-Control: public, max-age=1800, must-revalidate”. This tells clients: “consider this fresh for 30 minutes, then check back before using it again”. Unless you force a refresh, the browser won’t re-request the file for 30 minutes, after which it will send a conditional GET request. If the file hasn’t changed, the servers returns a “304 Not Modified” and the same Cache-Control header. This tells the browser to keep using the cache for another half hour and check back again. (Yes, half an hour is too short, I’ll change this later.)
Well, Safari 4 beta re-requests the file each time it needs it, seemingly ignoring the “max-age” directive. At least it’s sending a conditional GET, but it’s still a waste of bandwidth and time.