Because I have no control on the contents of the site, I would like to tell the browser to check for new pages once a day.
I tried with the set-cache-control max-age=86400 parameter, this works only for the first day. Afterwards the browser keeps asking for the page, and the webserver responds with a 304 use local copy. There are no new directions in the http headers.
Seems to be working correctly though. You told the browser to use the local version for maxage=86400. After this time the browser began asking the server if it should refresh. It does this with an "If-modified" header. The server replies that the browser should use its locally cached version rather than transfer a fresh copy.
If you want to force the browser to grab a new version you need to explore the "Expires" header. There's no good way to do this out-of-the-box, but a simple NSAPI could be created that would set the Expires header to now+24Hours.
I had the same problem. The problem is that the server when it sends the 304 response doesn't set a new Expires or Cache-Control header. The result of this all is that the client browser keeps asking if a modified version of the URL exists.
I have writen a SAF which set an Expires header in a 304 response. If you wish I can send you the source code.
I am in the same boat as the original writer of this note.
I would love to have the source code and see if it will solve my exact same problem. I am running WebServer v6.0sp6 and this problem is killing our CGI app's repsonse time. This 3rd party web app has a ton of GIFs.
This would (in my humble opinion) work best as a PathCheck. It would fairly trivial to adapt this to accept different values from the obj.conf, and to only do stuff in certain situations (e.g. when a 304 is the result code).
I am certain there are more fleshed out implementations of this in NSAPI form on the net.
I have already emailed the source code to an interested person. The code is a more detailed version of the code from Joe. I got that code on a request I send to technical support at Sun (case 36586182). Here is my code:
int kpn_set_cacheable(pblock *pb, Session *sn, Request *rq)
{
char *max_age = 0;
rq->directive_is_cacheable = 1;
pblock_nvinsert("Cache-control", "public", rq->srvhdrs);
max_age = pblock_findval("max-age", pb);
if (max_age)
{
time_t max_age_t = 0;
if (sscanf(max_age, "%ld", &max_age_t) == 1)
{
char expires_string[128];
time_t cur_time = 0;
struct tm expires, *expires_ptr;
char *ifmodifiedsince = 0;
char *path, modified_time[128];
struct stat *finfo;
time_t tp;
struct tm res, *resp;
if (rq->protv_num > 101) /* Must be HTTP/1.1 or lower */
{
log_error(LOG_INFORM, "set_cacheable", sn, rq, "protocol higher as HTTP/1.1: %d",
rq->protv_num );
return REQ_NOACTION;
}
cur_time = time(0);
cur_time += max_age_t;
expires_ptr = system_gmtime(&cur_time, &expires);
util_strftime(expires_string, HTTP_DATE_FMT, &expires);
param_free(pblock_remove("expires", rq->srvhdrs));
pblock_nvinsert("expires", expires_string, rq->srvhdrs);
/* determine 200 or 304 */
ifmodifiedsince = pblock_findval("if-modified-since", rq->headers);
if (ifmodifiedsince == 0)
{
return REQ_NOACTION; /* 200 response */
}
path = pblock_findval("path", rq->vars);
if (!(finfo = request_stat_path(path, rq)))
{
log_error(LOG_INFORM, "set_cacheable", sn, rq, "could not stat path %s", path);
return REQ_NOACTION;
}
tp = finfo->st_mtime;
resp = system_gmtime(&tp, &res);
if (util_later_than(&res, ifmodifiedsince) == 1) /* if not modified since */
{
protocol_status(sn, rq, PROTOCOL_NOT_MODIFIED, NULL);
/* We need to get rid of the superfluous HTTP headers. */
param_free(pblock_remove("content-type", rq->srvhdrs));
param_free(pblock_remove("Cache-control", rq->srvhdrs));
/* send response here, do not let the iplanet internal function do this.
The internal function strips of the 'Expires' header */
if (protocol_start_response(sn, rq) == REQ_ABORTED)
{
log_error(LOG_INFORM, "set_cacheable", sn, rq, "Could not send HTTP response");
return REQ_ABORTED;
}
return REQ_PROCEED;
}
}
}
return REQ_NOACTION;
}
Note that in order to use this code, the server cache must be disabled for the concerned files.
The code can be loaded with (in magnus.conf for > 6.0)
And can be used with (in obj.conf):
<Object ppath="<path>/images/">
# expires in 26 weeks
Service fn="kpn-set-cacheable" max-age="15724800"
Service method="(GET|HEAD)" type="~magnus-internal/*" fn="send-file" nocache=""
</object>
Note that I use the Service function and that two (2) service functions are called in order to return a 200 result code. This is in violation with the NSAPI guide, but is functioning in iPlanet 4.1 and in Sun ONE 6.0
I'm a little confused by some of the things being done here. It seems to be doing more than it needs to.
1) Cache-control: public
This header tells all caches (browsers, proxies, etc) that the content is cachable. Are you sure you want this? This stands a good chance of unexpectedly overiding the intended Cache-control header that may be set by other SAFs. (Aside - you should not insert with mixed case - the header name in pblock should be all lower case).
2) 200 v 304
The server already figures out if a 200 or a 304 is appropriate. Why are we duplicating this effort?
3) Why does this SAF need to start the response? That seems excessive. I am also not aware of the server's internal functions stripping headers from responses. Can you provide any more detail on that?
4) Disable Server cache
I don't understand the need for this. Can you elaborate a bit?
Note: this SAF was developed against the iPlanet 4.0/4.1 webserver.
1) Cache-control: public
This header tells all caches (browsers, proxies, etc)
that the content is cachable. Are you sure you want
this? This stands a good chance of unexpectedly
overiding the intended Cache-control header that may
be set by other SAFs. (Aside - you should not insert
with mixed case - the header name in pblock should be
all lower case).
This is the whole intention of this function. We want the object to be cacheable by browser/proxies. We also want to control when they have to check for a newer version. We accomplish that by setting both cache-control: public and by setting the Expires header. I tried using the iPlanet 4.1 build-in functionality, but that didn't work with 304 return codes. The iPlanet 4.1 server didn't allow to set the Expires header, only the 'Cache-control: max-age' can be set using the standard SAFs. Which is not recognized by HTTP/1.0 clients.
I could live with a 'Cache-control: max-age' and no Expires headers situation. If all was functioning well (most browsers are HTTP/1.1).
As a side-note. I have observed that other SAFs (eg. from appservers) do also set mixed-case headers. When dealing with these headers in my SAFs, I have the need for a new pblock_findval function (and alike). That function should search for the parameter ignoring the case (compliant with the spec for HTTP headers)
2) 200 v 304
The server already figures out if a 200 or a 304 is
appropriate. Why are we duplicating this effort?
Because the iPlanet 4.1server doesn't set an Expires header (or Cache-control header) on a 304 response.
3) Why does this SAF need to start the response? That
seems excessive. I am also not aware of the server's
internal functions stripping headers from responses.
Can you provide any more detail on that?
I tried the following (from memory):
- started with the common SAF for setting cache-control and the expires header. If I remember correctly the Expires/Cache-control header is send with a 200 response, but is removed with a 304 response.
- disable the server cache: same result
- wrote 1st version of this SAF, only set the expires header. Seems to be working, only a couple of weeks a noticeable increase in the number of 304 was observed.
- wrote current version of this SAF. Everything OK.
- enabled server cache: no expires/cache-control headers are sent. SAF isn't called.
- Did some experiments to minimize the SAF, eg. not start the response in this SAF, result the expires header was stripped from the 304 response.
4) Disable Server cache
I don't understand the need for this. Can you
elaborate a bit?