Bug Syncronicity
Thursday, 13 April 2006
Once in a while, two bugs line up perfectly. Let me explain.
Apache
Apache’s ap_meets_conditions in http_protocol.c is responsible for handling conditional HTTP requests. If you send Apache an If-Modified-Since, this code will figure out whether or not it’s been modified since the time you last saw it.
It does this by comparing the time in the IMS header to the modification time — mtime in the code — of the resource.
if ((ims >= mtime) && (ims <= r->request-time)) [
return HTTP_NOT_MODIFIED;
}
This works fine usually, but if the resource doesn’t have any notion of what a modification time is, Apache will fake it by using the current time.
mtime = (r->mtime != 0) ? r->mtime : time(NULL);
That way, if the If-Modified-Since time is now, Apache will automatically do the right thing (since IMS-based validation has a resolution of one second).
The only way this could be a problem is if a spurious If-Modified-Since is sent with the current date in it, in which case Apache will send a 304, even when the resource doesn’t even have a concept of modification times. But that isn’t a problem, because browsers won’t send an If-Modified-Since unless the resource previously gave them a Last-Modified header.
Right?
Safari
It turns out browsers are not the most well-behaved members of the HTTP bestiary. In the past, there’s been a lot of confusion about the Last-Modified header, as well as the Date header, but AFAIK that was cleared up in the late 90’s.
Then came Safari.
If Safari doesn’t see a Last-Modified header from a resource, it’ll send an If-Modified-Since header with the value of the Date header in it.
The mind boggles. I can’t imagine why the Apple guys thought this was a good idea, given that it’s forbidden by HTTP.
Why does this matter?
The Apache issue is fairly innocuous; they made an implementation decision to trust the browser not to be stupid. However, in combination with the Safari bug, it essentially creates a race condition.
For example, CGI scripts which have no concept of Last-Modified will, under some conditions, be returning a 304 Not Modified to Safari. If the state of your resource changes more than once a second — say, you’re doing some really fancy AJAX stuff — this would effectively throttle it to only appear to change once a second.
Is that a common case? You’d think not, but I tripped across it doing exactly that, and it’s likely there are other side effects of this frankly bizarre behaviour that aren’t apparent now.
When I saw this, my eyes nearly fell out of their sockets, and I was sure I was doing something wrong, but tcpflow doesn’t lie. That said, it’s hard to reproduce the Apache behaviour, and I’d like to see independent confirmation of both problems.
I’ve filed a bug with Apache; it details how to reproduce the behaviour (and I’d love to see some independent confirmations). I tried to file one for Safari with Bugreporter as well, but it appears to require a full ADC membership now, not my chintzy free one. So, I used the press-and-pray Safari bug report menu item.
Hopefully, this will get fixed soon; in the meantime, don’t assume that anything on the Web can be updated more than once a second.
