Changes with Apache 2.4.24
+ *) mod_socache_memcache: Pass expiration time through to memcached.
+ [Faidon Liambotis <paravoid debian.org>, Joe Orton]
+
+ *) mod_cache: Use the actual URI path and query-string for identifying the
+ cached entity (key), such that rewrites are taken into account when
+ running afterwards (CacheQuickHandler off). PR 21935. [Yann Ylavic]
+
*) mod_http2: new directive 'H2EarlyHints' to enable sending of HTTP status
103 interim responses. Disabled by default. [Stefan Eissing]
PATCHES ACCEPTED TO BACKPORT FROM TRUNK:
[ start all new proposals below, under PATCHES PROPOSED. ]
- *) mod_cache: Bring up-to-date w/ trunk.
- trunk patch:
- try to use the key of a possible open but stale cache entry (PR 50317)
- http://svn.apache.org/r1597533
- socache_mc_store: Pass through expiration time.
- http://svn.apache.org/r1649491
- Preserve the Content-Type in case of 304 response
- http://svn.apache.org/r1665216
- Use the actual URI path and query-string (PR 21935)
- http://svn.apache.org/r1756553
- http://svn.apache.org/r1756631
- better s-maxage support
- http://svn.apache.org/r1726675
- http://svn.apache.org/r1718496
- http://svn.apache.org/r1718476
- Rename ap_casecmpstr[n]() to ap_cstr_casecmp[n](), update with APR doxygen
- http://svn.apache.org/r1747469
- 2.4.x patch: http://home.apache.org/~jim/patches/httpd-2.4-cache.patch.txt
- +1: jim, ylavic, wrowe
- ylavic: r1756553 and r1649491 have a CHANGES entry.
-
*) ap_reclaim_child_processes(): Implement terminate immediately
trunk patches:
https://svn.apache.org/r1757061
static int filter_header_do(void *v, const char *key, const char *val)
{
- if ((*key == 'W' || *key == 'w') && !strcasecmp(key, "Warning")
+ if ((*key == 'W' || *key == 'w') && !ap_cstr_casecmp(key, "Warning")
&& *val == '1') {
/* any stored Warning headers with warn-code 1xx (see section
* 14.46) MUST be deleted from the cache entry and the forwarded
}
static int remove_header_do(void *v, const char *key, const char *val)
{
- if ((*key == 'W' || *key == 'w') && !strcasecmp(key, "Warning")) {
+ if ((*key == 'W' || *key == 'w') && !ap_cstr_casecmp(key, "Warning")) {
/* any stored Warning headers with warn-code 2xx MUST be retained
* in the cache entry and the forwarded response.
*/
}
static apr_status_t cache_canonicalise_key(request_rec *r, apr_pool_t* p,
- const char *uri, apr_uri_t *parsed_uri, const char **key)
+ const char *uri, const char *query,
+ apr_uri_t *parsed_uri,
+ const char **key)
{
cache_server_conf *conf;
char *port_str, *hn, *lcs;
* if needed.
*/
path = uri;
- querystring = parsed_uri->query;
+ querystring = apr_pstrdup(p, query ? query : parsed_uri->query);
if (conf->ignore_session_id->nelts) {
int i;
char **identifier;
/*
* Check if the identifier is in the querystring and cut it out.
*/
- if (querystring) {
+ if (querystring && *querystring) {
/*
* First check if the identifier is at the beginning of the
* querystring and followed by a '='
* identifier with a '&' and append a '='
*/
complete = apr_pstrcat(p, "&", *identifier, "=", NULL);
- param = strstr(querystring, complete);
+ param = ap_strstr_c(querystring, complete);
/* If we found something we are sitting on the '&' */
if (param) {
param++;
apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
const char **key)
{
- return cache_canonicalise_key(r, p, r->uri, &r->parsed_uri, key);
+ /* We want the actual query-string, which may differ from
+ * r->parsed_uri.query (immutable), so use "" (not NULL).
+ */
+ const char *args = r->args ? r->args : "";
+ return cache_canonicalise_key(r, p, r->uri, args, &r->parsed_uri, key);
}
/*
location = apr_table_get(r->headers_out, "Location");
if (location) {
- if (APR_SUCCESS != apr_uri_parse(r->pool, location, &location_uri)
- || APR_SUCCESS
- != cache_canonicalise_key(r, r->pool, location,
- &location_uri, &location_key)
- || !(r->parsed_uri.hostname && location_uri.hostname
- && !strcmp(r->parsed_uri.hostname,
+ if (apr_uri_parse(r->pool, location, &location_uri)
+ || cache_canonicalise_key(r, r->pool,
+ location, NULL,
+ &location_uri, &location_key)
+ || !(r->parsed_uri.hostname
+ && location_uri.hostname
+ && !strcmp(r->parsed_uri.hostname,
location_uri.hostname))) {
location_key = NULL;
}
content_location = apr_table_get(r->headers_out, "Content-Location");
if (content_location) {
- if (APR_SUCCESS
- != apr_uri_parse(r->pool, content_location,
- &content_location_uri)
- || APR_SUCCESS
- != cache_canonicalise_key(r, r->pool, content_location,
- &content_location_uri, &content_location_key)
- || !(r->parsed_uri.hostname && content_location_uri.hostname
- && !strcmp(r->parsed_uri.hostname,
+ if (apr_uri_parse(r->pool, content_location,
+ &content_location_uri)
+ || cache_canonicalise_key(r, r->pool,
+ content_location, NULL,
+ &content_location_uri,
+ &content_location_key)
+ || !(r->parsed_uri.hostname
+ && content_location_uri.hostname
+ && !strcmp(r->parsed_uri.hostname,
content_location_uri.hostname))) {
content_location_key = NULL;
}
* in "filter". All but the path comparisons are case-insensitive.
*/
static int uri_meets_conditions(const apr_uri_t *filter, const int pathlen,
- const apr_uri_t *url)
+ request_rec *r)
{
+ const apr_uri_t *url = &r->parsed_uri;
/* Scheme, hostname port and local part. The filter URI and the
* URI we test may have the following shapes:
}
else {
/* The URI scheme must be present and identical except for case. */
- if (!url->scheme || strcasecmp(filter->scheme, url->scheme)) {
+ if (!url->scheme || ap_cstr_casecmp(filter->scheme, url->scheme)) {
return 0;
}
/* For HTTP caching purposes, an empty (NULL) path is equivalent to
* a single "/" path. RFCs 3986/2396
*/
- if (!url->path) {
+ if (!r->uri) {
if (*filter->path == '/' && pathlen == 1) {
return 1;
}
/* Url has met all of the filter conditions so far, determine
* if the paths match.
*/
- return !strncmp(filter->path, url->path, pathlen);
+ return !strncmp(filter->path, r->uri, pathlen);
}
static cache_provider_list *get_provider(request_rec *r, struct cache_enable *ent,
}
cache_provider_list *cache_get_providers(request_rec *r,
- cache_server_conf *conf,
- apr_uri_t uri)
+ cache_server_conf *conf)
{
cache_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &cache_module);
cache_provider_list *providers = NULL;
for (i = 0; i < conf->cachedisable->nelts; i++) {
struct cache_disable *ent =
(struct cache_disable *)conf->cachedisable->elts;
- if (uri_meets_conditions(&ent[i].url, ent[i].pathlen, &uri)) {
+ if (uri_meets_conditions(&ent[i].url, ent[i].pathlen, r)) {
/* Stop searching now. */
return NULL;
}
for (i = 0; i < conf->cacheenable->nelts; i++) {
struct cache_enable *ent =
(struct cache_enable *)conf->cacheenable->elts;
- if (uri_meets_conditions(&ent[i].url, ent[i].pathlen, &uri)) {
+ if (uri_meets_conditions(&ent[i].url, ent[i].pathlen, r)) {
providers = get_provider(r, &ent[i], providers);
}
}
/* create the key if it doesn't exist */
if (!cache->key) {
- cache_generate_key(r, r->pool, &cache->key);
+ cache_handle_t *h;
+ /*
+ * Try to use the key of a possible open but stale cache
+ * entry if we have one.
+ */
+ if (cache->handle != NULL) {
+ h = cache->handle;
+ }
+ else {
+ h = cache->stale_handle;
+ }
+ if ((h != NULL) &&
+ (h->cache_obj != NULL) &&
+ (h->cache_obj->key != NULL)) {
+ cache->key = apr_pstrdup(r->pool, h->cache_obj->key);
+ }
+ else {
+ cache_generate_key(r, r->pool, &cache->key);
+ }
}
/* create a hashed filename from the key, and save it for later */
status = apr_stat(&finfo, lockname,
APR_FINFO_MTIME | APR_FINFO_NLINK, r->pool);
if (!(APR_STATUS_IS_ENOENT(status)) && APR_SUCCESS != status) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EEXIST, r, APLOGNO(00779)
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00779)
"Could not stat a cache lock file: %s",
lockname);
return status;
char *header = apr_pstrdup(r->pool, pragma_header);
const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last);
while (token) {
- /* handle most common quickest case... */
- if (!strcmp(token, "no-cache")) {
- cc->no_cache = 1;
- }
- /* ...then try slowest case */
- else if (!strcasecmp(token, "no-cache")) {
+ if (!ap_cstr_casecmp(token, "no-cache")) {
cc->no_cache = 1;
}
token = cache_strqtok(NULL, CACHE_SEPARATOR, &last);
switch (token[0]) {
case 'n':
case 'N': {
- /* handle most common quickest cases... */
- if (!strcmp(token, "no-cache")) {
- cc->no_cache = 1;
- }
- else if (!strcmp(token, "no-store")) {
- cc->no_store = 1;
- }
- /* ...then try slowest cases */
- else if (!strncasecmp(token, "no-cache", 8)) {
+ if (!ap_cstr_casecmpn(token, "no-cache", 8)) {
if (token[8] == '=') {
cc->no_cache_header = 1;
}
else if (!token[8]) {
cc->no_cache = 1;
}
- break;
}
- else if (!strcasecmp(token, "no-store")) {
+ else if (!ap_cstr_casecmp(token, "no-store")) {
cc->no_store = 1;
}
- else if (!strcasecmp(token, "no-transform")) {
+ else if (!ap_cstr_casecmp(token, "no-transform")) {
cc->no_transform = 1;
}
break;
}
case 'm':
case 'M': {
- /* handle most common quickest cases... */
- if (!strcmp(token, "max-age=0")) {
- cc->max_age = 1;
- cc->max_age_value = 0;
- }
- else if (!strcmp(token, "must-revalidate")) {
- cc->must_revalidate = 1;
- }
- /* ...then try slowest cases */
- else if (!strncasecmp(token, "max-age", 7)) {
+ if (!ap_cstr_casecmpn(token, "max-age", 7)) {
if (token[7] == '='
&& !apr_strtoff(&offt, token + 8, &endp, 10)
&& endp > token + 8 && !*endp) {
cc->max_age = 1;
cc->max_age_value = offt;
}
- break;
}
- else if (!strncasecmp(token, "max-stale", 9)) {
+ else if (!ap_cstr_casecmp(token, "must-revalidate")) {
+ cc->must_revalidate = 1;
+ }
+ else if (!ap_cstr_casecmpn(token, "max-stale", 9)) {
if (token[9] == '='
&& !apr_strtoff(&offt, token + 10, &endp, 10)
&& endp > token + 10 && !*endp) {
cc->max_stale = 1;
cc->max_stale_value = -1;
}
- break;
}
- else if (!strncasecmp(token, "min-fresh", 9)) {
+ else if (!ap_cstr_casecmpn(token, "min-fresh", 9)) {
if (token[9] == '='
&& !apr_strtoff(&offt, token + 10, &endp, 10)
&& endp > token + 10 && !*endp) {
cc->min_fresh = 1;
cc->min_fresh_value = offt;
}
- break;
- }
- else if (!strcasecmp(token, "must-revalidate")) {
- cc->must_revalidate = 1;
}
break;
}
case 'o':
case 'O': {
- if (!strcasecmp(token, "only-if-cached")) {
+ if (!ap_cstr_casecmp(token, "only-if-cached")) {
cc->only_if_cached = 1;
}
break;
}
case 'p':
case 'P': {
- /* handle most common quickest cases... */
- if (!strcmp(token, "private")) {
- cc->private = 1;
- }
- /* ...then try slowest cases */
- else if (!strcasecmp(token, "public")) {
+ if (!ap_cstr_casecmp(token, "public")) {
cc->public = 1;
}
- else if (!strncasecmp(token, "private", 7)) {
+ else if (!ap_cstr_casecmpn(token, "private", 7)) {
if (token[7] == '=') {
cc->private_header = 1;
}
else if (!token[7]) {
cc->private = 1;
}
- break;
}
- else if (!strcasecmp(token, "proxy-revalidate")) {
+ else if (!ap_cstr_casecmp(token, "proxy-revalidate")) {
cc->proxy_revalidate = 1;
}
break;
}
case 's':
case 'S': {
- if (!strncasecmp(token, "s-maxage", 8)) {
+ if (!ap_cstr_casecmpn(token, "s-maxage", 8)) {
if (token[8] == '='
&& !apr_strtoff(&offt, token + 9, &endp, 10)
&& endp > token + 9 && !*endp) {
cc->s_maxage = 1;
cc->s_maxage_value = offt;
}
- break;
}
break;
}
switch (token[0]) {
case 'n':
case 'N': {
- if (!strncmp(token, "no-cache", 8)
- || !strncasecmp(token, "no-cache", 8)) {
+ if (!ap_cstr_casecmpn(token, "no-cache", 8)) {
if (token[8] == '=') {
const char *header = cache_strqtok(token + 9,
CACHE_SEPARATOR "\"", &slast);
}
case 'p':
case 'P': {
- if (!strncmp(token, "private", 7)
- || !strncasecmp(token, "private", 7)) {
+ if (!ap_cstr_casecmpn(token, "private", 7)) {
if (token[7] == '=') {
const char *header = cache_strqtok(token + 8,
CACHE_SEPARATOR "\"", &slast);
cache_request_rec *cache, request_rec *r, apr_bucket_brigade *bb);
cache_provider_list *cache_get_providers(request_rec *r,
- cache_server_conf *conf, apr_uri_t uri);
+ cache_server_conf *conf);
/**
* Get a value from a table, where the table may contain multiple
/*
* Which cache module (if any) should handle this request?
*/
- if (!(providers = cache_get_providers(r, conf, r->parsed_uri))) {
+ if (!(providers = cache_get_providers(r, conf))) {
return DECLINED;
}
/*
* Which cache module (if any) should handle this request?
*/
- if (!(providers = cache_get_providers(r, conf, r->parsed_uri))) {
+ if (!(providers = cache_get_providers(r, conf))) {
return DECLINED;
}
/* Have we received a 304 response without any headers at all? Fall back to
* the original headers in the original cached request.
*/
- if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle && !cc_out
- && !pragma) {
- cc_out = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs,
- "Cache-Control");
- pragma = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs,
- "Pragma");
+ if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle) {
+ if (!cc_out && !pragma) {
+ cc_out = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs,
+ "Cache-Control");
+ pragma = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs,
+ "Pragma");
+ }
+
+ /* 304 does not contain Content-Type and mod_mime regenerates the
+ * Content-Type based on the r->filename. This would lead to original
+ * Content-Type to be lost (overwriten by whatever mod_mime generates).
+ * We preserves the original Content-Type here. */
+ ap_set_content_type(r, apr_table_get(
+ cache->stale_handle->resp_hdrs, "Content-Type"));
}
/* Parse the cache control header */
* include the following: an Expires header (section 14.21); a
* "max-age", "s-maxage", "must-revalidate", "proxy-revalidate",
* "public" or "private" cache-control directive (section 14.9).
+ *
+ * FIXME: Wrong if cc_out has just an extension we don't know about
*/
}
else {
/* if a broken Expires header is present, don't cache it */
reason = apr_pstrcat(p, "Broken expires header: ", exps, NULL);
}
- else if (!dconf->store_expired && exp != APR_DATE_BAD
+ else if (!control.s_maxage && !control.max_age
+ && !dconf->store_expired && exp != APR_DATE_BAD
&& exp < r->request_time) {
- /* if a Expires header is in the past, don't cache it */
+ /* if a Expires header is in the past, don't cache it
+ * Unless CC: s-maxage or max-age is present
+ */
reason = "Expires header already expired; not cacheable";
}
else if (!dconf->store_expired && (control.must_revalidate
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02473)
"cache: %s responded with an uncacheable 304, "
- "retrying the request. Reason: %s",
- r->unparsed_uri, reason);
+ "retrying the request %s. Reason: %s",
+ cache->key, r->unparsed_uri, reason);
/* we've got a cache conditional miss! tell anyone who cares */
cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS,
if (reason) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00768)
- "cache: %s not cached. Reason: %s", r->unparsed_uri,
- reason);
+ "cache: %s not cached for request %s. Reason: %s",
+ cache->key, r->unparsed_uri, reason);
/* we've got a cache miss! tell anyone who cares */
cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS,
}
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00769)
- "cache: Caching url: %s", r->unparsed_uri);
+ "cache: Caching url %s for request %s",
+ cache->key, r->unparsed_uri);
/* We are actually caching this response. So it does not
* make sense to remove this entity any more.
"replacing with now");
}
+
+ /* CC has priority over Expires. */
+ if (control.s_maxage || control.max_age) {
+ apr_int64_t x;
+
+ x = control.s_maxage ? control.s_maxage_value : control.max_age_value;
+ x = x * MSEC_ONE_SEC;
+
+ if (x < dconf->minex) {
+ x = dconf->minex;
+ }
+ if (x > dconf->maxex) {
+ x = dconf->maxex;
+ }
+ exp = date + x;
+ }
+
/* if no expiry date then
+ * if Cache-Control: s-maxage
+ * expiry date = date + smaxage
* if Cache-Control: max-age
* expiry date = date + max-age
* else if lastmod
* else
* expire date = date + defaultexpire
*/
- if (exp == APR_DATE_BAD) {
-
- if (control.max_age) {
- apr_int64_t x;
- errno = 0;
- x = control.max_age_value;
- if (errno) {
- x = dconf->defex;
- }
- else {
- x = x * MSEC_ONE_SEC;
- }
- if (x < dconf->minex) {
- x = dconf->minex;
- }
- if (x > dconf->maxex) {
- x = dconf->maxex;
- }
- exp = date + x;
- }
- else if ((lastmod != APR_DATE_BAD) && (lastmod < date)) {
+ if (exp == APR_DATE_BAD) {
+ if ((lastmod != APR_DATE_BAD) && (lastmod < date)) {
/* if lastmod == date then you get 0*conf->factor which results in
* an expiration time of now. This causes some problems with
* freshness calculations, so we choose the else path...
/* let someone else attempt to cache */
cache_remove_lock(conf, cache, r, NULL);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02971)
+ "cache: serving %s (revalidated)", r->uri);
+
return ap_pass_brigade(f->next, bb);
}
/* ### What about APR_EOF? */
rv = apr_file_gets(w, MAX_STRING_LEN - 1, file);
if (rv != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00717)
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00717)
"Premature end of cache headers.");
return rv;
}
static apr_status_t recall_headers(cache_handle_t *h, request_rec *r)
{
disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
+ apr_status_t rv;
/* This case should not happen... */
if (!dobj->hdrs.fd) {
h->resp_hdrs = apr_table_make(r->pool, 20);
/* Call routine to read the header lines/status line */
- read_table(h, r, h->resp_hdrs, dobj->hdrs.fd);
- read_table(h, r, h->req_hdrs, dobj->hdrs.fd);
+ rv = read_table(h, r, h->resp_hdrs, dobj->hdrs.fd);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02987)
+ "Error reading response headers from %s for %s",
+ dobj->hdrs.file, dobj->name);
+ }
+ rv = read_table(h, r, h->req_hdrs, dobj->hdrs.fd);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02988)
+ "Error reading request headers from %s for %s",
+ dobj->hdrs.file, dobj->name);
+ }
apr_file_close(dobj->hdrs.fd);
int errstatus;
int rc = OK;
- /* XXX: not sure if this is right yet
- * see comment in http_core.c:default_handler
+ /* Bail out if r->handler isn't the default value, and doesn't look like a Content-Type
+ * XXX: Even though we made the user explicitly list each path to cache?
*/
- if (ap_strcmp_match(r->handler, "*/*")) {
+ if (ap_strcmp_match(r->handler, "*/*") && !AP_IS_DEFAULT_HANDLER_NAME(r->handler)) {
return DECLINED;
}
return APR_EINVAL;
}
- /* In APR-util - unclear what 'timeout' is, as it was not implemented */
- rv = apr_memcache_set(ctx->mc, buf, (char*)ucaData, nData, 0, 0);
+ /* memcache needs time in seconds till expiry; fail if this is not
+ * positive *before* casting to unsigned (apr_uint32_t). */
+ expiry -= apr_time_now();
+ if (apr_time_sec(expiry) <= 0) {
+ return APR_EINVAL;
+ }
+ rv = apr_memcache_set(ctx->mc, buf, (char*)ucaData, nData,
+ apr_time_sec(expiry), 0);
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00790)