HTTP_ConditionalGet

Last-Modified + Expires

Here we set a static "lastModifiedTime" and "maxAge" to 20. The browser will consider this document fresh for 20 seconds, then revalidate its cache. After the 304 response, the cache will be good for another 20 seconds. Unless you force a reload, there will only be 304 responses for this page after the initial download.

Notes

How to distinguish 200 and 304 responses

For these pages all 200 responses are sent in chunks a second apart, so you should notice that 304 responses are quicker. You can also use HTTP sniffers like Fiddler (win) and LiveHTTPHeaders (Firefox add-on) to verify headers and content being sent.

Browser notes

Opera
Opera has a couple behaviors against the HTTP spec: Manual refreshes (F5) prevents the ETag/If-Modified-Since headers from being sent; it only sends them when following a link or bookmark. Also, Opera will not honor the must-revalidate Cache-Control value unless max-age is set. To get Opera to follow the spec, ConditionalGet will send Opera max-age=0 (if one is not already set).
Safari
ETag validation is unsupported, but Safari supports HTTP/1.0 validation via If-Modified-Since headers as long as the cache is explicitly marked "public" or "private" ("private" is default in ConditionalGet).