This patch is generated from the etag branch of HEAD in squid Wed Apr 6 02:19:09 2005 GMT See http://devel.squid-cache.org/ Index: squid/src/ETag.c diff -u squid/src/ETag.c:1.4 squid/src/ETag.c:removed --- squid/src/ETag.c:1.4 Fri Jan 12 00:20:32 2001 +++ squid/src/ETag.c Tue Apr 5 19:19:32 2005 @@ -1,68 +0,0 @@ - -/* - * $Id$ - * - * DEBUG: none ETag parsing support - * AUTHOR: Alex Rousskov - * - * SQUID Web Proxy Cache http://www.squid-cache.org/ - * ---------------------------------------------------------- - * - * Squid is the result of efforts by numerous individuals from - * the Internet community; see the CONTRIBUTORS file for full - * details. Many organizations have provided support for Squid's - * development; see the SPONSORS file for full details. Squid is - * Copyrighted (C) 2001 by the Regents of the University of - * California; see the COPYRIGHT file for full details. Squid - * incorporates software developed and/or copyrighted by other - * sources; see the CREDITS file for full details. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. - * - */ - -#include "squid.h" - -/* - * Note: ETag is not an http "field" like, for example HttpHdrRange. ETag is a - * field-value that maybe used in many http fields. - */ - -/* parses a string as weak or strong entity-tag; returns true on success */ -/* note: we do not duplicate "str"! */ -int -etagParseInit(ETag * etag, const char *str) -{ - int len; - assert(etag && str); - etag->str = NULL; - etag->weak = !strncmp(str, "W/", 2); - if (etag->weak) - str += 2; - /* check format (quoted-string) */ - len = strlen(str); - if (len >= 2 && str[0] == '"' && str[len - 1] == '"') - etag->str = str; - return etag->str != NULL; -} - -/* returns true if etags are equal */ -int -etagIsEqual(const ETag * tag1, const ETag * tag2) -{ - assert(tag1 && tag2); - assert(!tag1->weak && !tag2->weak); /* weak comparison not implemented yet */ - return !strcmp(tag1->str, tag2->str); -} Index: squid/src/HttpHeader.c diff -u squid/src/HttpHeader.c:1.16 squid/src/HttpHeader.c:1.6.18.3 --- squid/src/HttpHeader.c:1.16 Sun Aug 11 14:45:40 2002 +++ squid/src/HttpHeader.c Thu Aug 15 11:30:38 2002 @@ -86,7 +86,7 @@ {"Content-Type", HDR_CONTENT_TYPE, ftStr}, {"Cookie", HDR_COOKIE, ftStr}, {"Date", HDR_DATE, ftDate_1123}, - {"ETag", HDR_ETAG, ftETag}, + {"ETag", HDR_ETAG, ftStr}, {"Expires", HDR_EXPIRES, ftDate_1123}, {"From", HDR_FROM, ftStr}, {"Host", HDR_HOST, ftStr}, @@ -953,18 +953,6 @@ return base64_decode(field); } -ETag -httpHeaderGetETag(const HttpHeader * hdr, http_hdr_type id) -{ - ETag etag = - {NULL, -1}; - HttpHeaderEntry *e; - assert(Headers[id].type == ftETag); /* must be of an appropriate type */ - if ((e = httpHeaderFindEntry(hdr, id))) - etagParseInit(&etag, strBuf(e->value)); - return etag; -} - TimeOrTag httpHeaderGetTimeOrTag(const HttpHeader * hdr, http_hdr_type id) { @@ -975,17 +963,20 @@ if ((e = httpHeaderFindEntry(hdr, id))) { const char *str = strBuf(e->value); /* try as an ETag */ - if (etagParseInit(&tot.tag, str)) { - tot.valid = tot.tag.str != NULL; + if (*str == '"' || (str[0] == 'W' && str[1] == '/')) { + tot.tag = str; tot.time = -1; + tot.valid = 1; } else { /* or maybe it is time? */ tot.time = parse_rfc1123(str); - tot.valid = tot.time >= 0; - tot.tag.str = NULL; + if (tot.time >= 0) + tot.valid = 1; + tot.tag = NULL; } + } else { + tot.time = -1; } - assert(tot.time < 0 || !tot.tag.str); /* paranoid */ return tot; } Index: squid/src/HttpReply.c diff -u squid/src/HttpReply.c:1.10 squid/src/HttpReply.c:1.7.14.3 --- squid/src/HttpReply.c:1.10 Wed Oct 24 02:42:11 2001 +++ squid/src/HttpReply.c Wed Oct 24 03:38:31 2001 @@ -210,7 +210,7 @@ httpPacked304Reply(const HttpReply * rep) { static const http_hdr_type ImsEntries[] = - {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER}; + {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, HDR_ETAG, /* eof */ HDR_OTHER}; int t; MemBuf mb; Packer p; Index: squid/src/HttpRequest.c diff -u squid/src/HttpRequest.c:1.7 squid/src/HttpRequest.c:1.7.14.2 --- squid/src/HttpRequest.c:1.7 Fri Apr 13 17:31:01 2001 +++ squid/src/HttpRequest.c Wed May 30 03:08:45 2001 @@ -67,6 +67,13 @@ httpHdrCcDestroy(req->cache_control); if (req->range) httpHdrRangeDestroy(req->range); + if (req->vary) { + if (req->etags == &req->vary->etags) + req->etags = NULL; + storeLocateVaryDone(req->vary); + } + assert(req->etags == NULL); + safe_free(req->etag); memFree(req, MEM_REQUEST_T); } Index: squid/src/Makefile.am diff -u squid/src/Makefile.am:1.23 squid/src/Makefile.am:1.1.10.6 --- squid/src/Makefile.am:1.23 Sat Jul 13 15:08:09 2002 +++ squid/src/Makefile.am Thu Aug 15 11:30:38 2002 @@ -125,7 +125,6 @@ $(DNSSOURCE) \ enums.h \ errorpage.c \ - ETag.c \ event.c \ external_acl.c \ fd.c \ Index: squid/src/client_side.c diff -u squid/src/client_side.c:1.67 squid/src/client_side.c:1.29.2.26 --- squid/src/client_side.c:1.67 Thu Aug 15 11:14:04 2002 +++ squid/src/client_side.c Thu Aug 15 11:42:49 2002 @@ -125,6 +125,7 @@ static int clientReplyBodyTooLarge(HttpReply *, ssize_t clen); static int clientRequestBodyTooLarge(int clen); static void clientProcessBody(ConnStateData * conn); +static int varyEvaluateMatch(StoreEntry * entry, request_t * request); static int checkAccelOnly(clientHttpRequest * http) @@ -366,11 +367,133 @@ } static void +clientHandleETagMiss(clientHttpRequest * http) +{ + StoreEntry *entry = http->entry; + MemObject *mem = entry->mem_obj; + request_t *request = http->request; + + if (mem->reply) { + const char *etag = httpHeaderGetStr(&mem->reply->header, HDR_ETAG); + if (etag) { + /* This has to match storeSetPublicKey, except for the key which is NULL */ + String vary = StringNull; + String varyhdr; + varyhdr = httpHeaderGetList(&mem->reply->header, HDR_VARY); + if (strBuf(varyhdr)) + strListAdd(&vary, strBuf(varyhdr), ','); + stringClean(&varyhdr); +#if X_ACCELERATOR_VARY + /* This needs to match the order in http.c:httpMakeVaryMark */ + varyhdr = httpHeaderGetList(&mem->reply->header, HDR_X_ACCELERATOR_VARY); + if (strBuf(varyhdr)) + strListAdd(&vary, strBuf(varyhdr), ','); + stringClean(&varyhdr); +#endif + storeAddVary(mem->url, mem->log_url, mem->method, NULL, httpHeaderGetStr(&mem->reply->header, HDR_ETAG), strBuf(vary), httpMakeVaryMark(request, mem->reply)); + stringClean(&vary); + } + } + request->done_etag = 1; + if (request->vary) { + storeLocateVaryDone(request->vary); + request->vary = NULL; + request->etags = NULL; /* pointed into request->vary */ + } + safe_free(request->etag); + safe_free(request->vary_headers); + storeUnregister(http->sc, entry, http); + storeUnlockObject(entry); + clientProcessRequest(http); +} + +static void +clientHandleETagReply(void *data, char *buf, ssize_t size) +{ + clientHttpRequest *http = data; + StoreEntry *entry = http->entry; + MemObject *mem; + const char *url = storeUrl(entry); + http_status status; + debug(33, 3) ("clientHandleETagReply: %s, %d bytes\n", url, (int) size); + if (entry == NULL) { + /* client aborted */ + return; + } + if (size < 0 && !EBIT_TEST(entry->flags, ENTRY_ABORTED)) { + clientHandleETagMiss(http); + return; + } + mem = entry->mem_obj; + status = mem->reply->sline.status; + if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { + debug(33, 3) ("clientHandleETagReply: ABORTED '%s'\n", url); + clientHandleETagMiss(http); + return; + } + if (STORE_PENDING == entry->store_status && 0 == status) { + debug(33, 3) ("clientHandleETagReply: Incomplete headers for '%s'\n", url); + if (size >= CLIENT_SOCK_SZ) { + /* will not get any bigger than that */ + debug(33, 3) ("clientHandleETagReply: Reply is too large '%s'\n", url); + clientHandleETagMiss(http); + } else { + storeClientCopy(http->sc, entry, + http->out.offset + size, + http->out.offset, + CLIENT_SOCK_SZ, + buf, + clientHandleETagReply, + http); + } + return; + } + if (HTTP_NOT_MODIFIED == mem->reply->sline.status) { + /* Remember the ETag and restart */ + clientHandleETagMiss(http); + return; + } + /* Send the new object to the client */ + clientSendMoreData(data, buf, size); + return; +} + +static void +clientProcessETag(clientHttpRequest * http) +{ + char *url = http->uri; + StoreEntry *entry = NULL; + debug(33, 3) ("clientProcessETag: '%s'\n", http->uri); + entry = storeCreateEntry(url, + http->log_uri, + http->request->flags, + http->request->method); + http->sc = storeClientListAdd(entry, http); +#if DELAY_POOLS + /* delay_id is already set on original store client */ + delaySetStoreClient(http->sc, delayClient(http)); +#endif + http->entry = entry; + http->out.offset = 0; + fwdStart(http->conn->fd, http->entry, http->request); + /* Register with storage manager to receive updates when data comes in. */ + if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) + debug(33, 0) ("clientProcessETag: found ENTRY_ABORTED object\n"); + storeClientCopy(http->sc, entry, + http->out.offset, + http->out.offset, + HTTP_REQBUF_SZ, http->reqbuf + clientHandleETagReply, + http); +} + +static void clientProcessExpired(void *data) { clientHttpRequest *http = data; char *url = http->uri; StoreEntry *entry = NULL; + const char *etag; debug(33, 3) ("clientProcessExpired: '%s'\n", http->uri); assert(http->entry->lastmod >= 0); /* @@ -410,6 +533,9 @@ debug(33, 5) ("clientProcessExpired: lastmod %ld\n", (long int) entry->lastmod); http->entry = entry; http->out.offset = 0; + etag = httpHeaderGetStr(&http->old_entry->mem_obj->reply->header, HDR_ETAG); + if (etag) + http->request->etag = xstrdup(etag); fwdStart(http->conn->fd, http->entry, http->request); /* Register with storage manager to receive updates when data comes in. */ if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) @@ -443,6 +569,20 @@ debug(33, 5) ("clientGetsOldEntry: NO, reply=%d\n", status); return 0; } + /* If the ETag matches the clients If-None-Match, then return + * the servers 304 reply + */ + if (httpHeaderHas(&new_entry->mem_obj->reply->header, HDR_ETAG) && + httpHeaderHas(&request->header, HDR_IF_NONE_MATCH)) { + const char *etag = httpHeaderGetStr(&new_entry->mem_obj->reply->header, HDR_ETAG); + String etags = httpHeaderGetList(&request->header, HDR_IF_NONE_MATCH); + int etag_match = strListIsMember(&etags, etag, ','); + stringClean(&etags); + if (etag_match) { + debug(33, 5) ("clientGetsOldEntry: NO, client If-None-Match\n"); + return 0; + } + } /* If the client did not send IMS in the request, then it * must get the old object, not this "Not Modified" reply */ if (!request->flags.ims) { @@ -1262,6 +1402,31 @@ } /* + * clientProcessVary is called when it is detected that a object + * varies and we need to get the correct variant + */ +static void +clientProcessVary(VaryData * vary, void *data) +{ + clientHttpRequest *http = data; + if (!vary) { + clientProcessRequest(http); + return; + } + if (vary->key) { + debug(33, 2) ("clientProcessVary: HIT key=%s etag=%s\n", vary->key, vary->etag); + } else { + int i; + debug(33, 2) ("clientProcessVary MISS\n"); + for (i = 0; i < vary->etags.count; i++) { + debug(33, 3) ("ETag: %s\n", (char *) vary->etags.items[i]); + } + } + http->request->vary = vary; + clientProcessRequest(http); +} + +/* * clientCacheHit should only be called until the HTTP reply headers * have been parsed. Normally this should be a single call, but * it might take more than one. As soon as we have the headers, @@ -1275,6 +1440,7 @@ StoreEntry *e = http->entry; MemObject *mem; request_t *r = http->request; + int is_modified = -1; debug(33, 3) ("clientCacheHit: %s, %d bytes\n", http->uri, (int) size); if (http->entry == NULL) { debug(33, 3) ("clientCacheHit: request aborted\n"); @@ -1332,19 +1498,27 @@ debug(33, 2) ("clientProcessHit: Vary MATCH!\n"); break; case VARY_OTHER: - /* This is not the correct entity for this request. We need - * to requery the cache. - */ - http->entry = NULL; - storeUnregister(http->sc, e, http); - http->sc = NULL; - storeUnlockObject(e); - /* Note: varyEvalyateMatch updates the request with vary information - * so we only get here once. (it also takes care of cancelling loops) - */ - debug(33, 2) ("clientProcessHit: Vary detected!\n"); - clientProcessRequest(http); - return; + { + /* This is not the correct entity for this request. We need + * to requery the cache. + */ + store_client *sc = http->sc; + http->entry = NULL; /* saved in e */ + /* Warning: storeUnregister may abort the object so we must + * call storeLocateVary before unregistering, and + * storeLocateVary may complete immediately so we cannot + * rely on the http structure for this... + */ + http->sc = NULL; + storeLocateVary(e, e->mem_obj->reply->hdr_sz, r->vary_headers, clientProcessVary, http); + storeUnregister(sc, e, http); + storeUnlockObject(e); + /* Note: varyEvalyateMatch updates the request with vary information + * so we only get here once. (it also takes care of cancelling loops) + */ + debug(33, 2) ("clientProcessHit: Vary detected!\n"); + return; + } case VARY_CANCEL: /* varyEvaluateMatch found a object loop. Process as miss */ debug(33, 1) ("clientProcessHit: Vary object loop!\n"); @@ -1362,7 +1536,10 @@ if (checkNegativeHit(e)) { http->log_type = LOG_TCP_NEGATIVE_HIT; clientSendMoreData(data, buf, size); - } else if (r->method == METHOD_HEAD) { + return; + } +#if THIS_IS_FALSE + if (r->method == METHOD_HEAD) { /* * RFC 2068 seems to indicate there is no "conditional HEAD" * request. We cannot validate a cached object for a HEAD @@ -1371,7 +1548,34 @@ if (e->mem_status == IN_MEMORY) http->log_type = LOG_TCP_MEM_HIT; clientSendMoreData(data, buf, size); - } else if (refreshCheckHTTP(e, r) && !http->flags.internal) { + return; + } +#endif + if (httpHeaderHas(&r->header, HDR_IF_MATCH)) { + String req_etags; + const char *rep_etag = httpHeaderGetStr(&e->mem_obj->reply->header, HDR_ETAG); + int has_etag; + if (!rep_etag) { + /* The cached object does not have a entity tag. This cannot + * be a hit for the requested object. + */ + http->log_type = LOG_TCP_MISS; + clientProcessMiss(http); + return; + } + req_etags = httpHeaderGetList(&http->request->header, HDR_IF_MATCH); + has_etag = strListIsMember(&req_etags, rep_etag, ','); + stringClean(&req_etags); + if (!has_etag) { + /* The entity tags does not match. This cannot be a + * hit for this object. Qyery the origin. + */ + http->log_type = LOG_TCP_MISS; + clientProcessMiss(http); + return; + } + } + if (refreshCheckHTTP(e, r) && !http->flags.internal) { debug(33, 5) ("clientCacheHit: in refreshCheck() block\n"); /* * We hold a stale copy; it needs to be validated @@ -1413,7 +1617,37 @@ http->log_type = LOG_TCP_MISS; clientProcessMiss(http); } - } else if (r->flags.ims) { + return; + } + if (httpHeaderHas(&r->header, HDR_IF_NONE_MATCH)) { + String req_etags; + const char *rep_etag = httpHeaderGetStr(&e->mem_obj->reply->header, HDR_ETAG); + int has_etag; + if (mem->reply->sline.status != HTTP_OK) { + debug(33, 4) ("clientCacheHit: Reply code %d != 200\n", + mem->reply->sline.status); + http->log_type = LOG_TCP_MISS; + clientProcessMiss(http); + return; + } + if (!rep_etag) { + /* The cached object does not have a entity tag, but the client + * obviously thinks there should be one... Query the origin to + * be on the safe side. + */ + http->log_type = LOG_TCP_MISS; + clientProcessMiss(http); + return; + } + req_etags = httpHeaderGetList(&http->request->header, HDR_IF_NONE_MATCH); + has_etag = strListIsMember(&req_etags, rep_etag, ','); + stringClean(&req_etags); + if (has_etag) { + http->log_type = LOG_TCP_IMS_HIT; + is_modified = 0; + } + } + if (is_modified != 0 && r->flags.ims) { /* * Handle If-Modified-Since requests from the client */ @@ -1422,38 +1656,41 @@ mem->reply->sline.status); http->log_type = LOG_TCP_MISS; clientProcessMiss(http); + return; } else if (modifiedSince(e, http->request)) { http->log_type = LOG_TCP_IMS_HIT; clientSendMoreData(data, buf, size); - } else { - time_t timestamp = e->timestamp; - MemBuf mb = httpPacked304Reply(e->mem_obj->reply); - http->log_type = LOG_TCP_IMS_HIT; - storeUnregister(http->sc, e, http); - http->sc = NULL; - storeUnlockObject(e); - e = clientCreateStoreEntry(http, http->request->method, null_request_flags); - /* - * Copy timestamp from the original entry so the 304 - * reply has a meaningful Age: header. - */ - e->timestamp = timestamp; - http->entry = e; - httpReplyParse(e->mem_obj->reply, mb.buf, mb.size); - storeAppend(e, mb.buf, mb.size); - memBufClean(&mb); - storeComplete(e); } - } else { + is_modified = 0; + } + if (is_modified == 0) { + time_t timestamp = e->timestamp; + MemBuf mb = httpPacked304Reply(e->mem_obj->reply); + http->log_type = LOG_TCP_IMS_HIT; + storeUnregister(http->sc, e, http); + http->sc = NULL; + storeUnlockObject(e); + e = clientCreateStoreEntry(http, http->request->method, null_request_flags); /* - * plain ol' cache hit + * Copy timestamp from the original entry so the 304 + * reply has a meaningful Age: header. */ - if (e->mem_status == IN_MEMORY) - http->log_type = LOG_TCP_MEM_HIT; - else if (Config.onoff.offline) - http->log_type = LOG_TCP_OFFLINE_HIT; - clientSendMoreData(data, buf, size); + e->timestamp = timestamp; + http->entry = e; + httpReplyParse(e->mem_obj->reply, mb.buf, mb.size); + storeAppend(e, mb.buf, mb.size); + memBufClean(&mb); + storeComplete(e); + return; } + /* + * plain ol' cache hit + */ + if (e->mem_status == IN_MEMORY) + http->log_type = LOG_TCP_MEM_HIT; + else if (Config.onoff.offline) + http->log_type = LOG_TCP_OFFLINE_HIT; + clientSendMoreData(data, buf, size); } @@ -1887,6 +2124,16 @@ if (NULL == e) { /* this object isn't in the cache */ debug(33, 3) ("clientProcessRequest2: storeGet() MISS\n"); + if (r->vary) { + if (r->done_etag) { + debug(33, 1) ("clientProcessRequest2: ETag loop\n"); + } else if (r->etags) { + debug(33, 2) ("clientProcessRequest2: ETag miss\n"); + r->etags = NULL; + } else if (r->vary->etags.count > 0) { + r->etags = &r->vary->etags; + } + } return LOG_TCP_MISS; } if (Config.onoff.offline) { @@ -2045,9 +2292,9 @@ return; } assert(http->out.offset == 0); - http->entry = clientCreateStoreEntry(http, r->method, r->flags); if (http->redirect.status) { HttpReply *rep = httpReplyCreate(); + http->entry = clientCreateStoreEntry(http, r->method, r->flags); #if LOG_TCP_REDIRECTS http->log_type = LOG_TCP_REDIRECT; #endif @@ -2058,6 +2305,11 @@ storeComplete(http->entry); return; } + if (r->etags) { + clientProcessETag(http); + return; + } + http->entry = clientCreateStoreEntry(http, r->method, r->flags); if (http->flags.internal) r->protocol = PROTO_INTERNAL; fwdStart(http->conn->fd, http->entry, r); @@ -3247,7 +3499,7 @@ NHttpSockets = 0; } -int +static int varyEvaluateMatch(StoreEntry * entry, request_t * request) { const char *vary = request->vary_headers; @@ -3289,6 +3541,7 @@ /* Ouch.. we cannot handle this kind of variance */ /* XXX This cannot really happen, but just to be complete */ return VARY_CANCEL; +#if NOT_FOR_ETAGS } else if (strcmp(vary, entry->mem_obj->vary_headers) == 0) { return VARY_MATCH; } else { @@ -3298,6 +3551,10 @@ debug(33, 1) ("varyEvaluateMatch: Oops. Not a Vary match on second attempt, '%s' '%s'\n", entry->mem_obj->url, vary); return VARY_CANCEL; +#else + } else { + return VARY_MATCH; +#endif } } } Index: squid/src/http.c diff -u squid/src/http.c:1.21 squid/src/http.c:1.13.14.11 --- squid/src/http.c:1.21 Fri Jun 7 15:43:02 2002 +++ squid/src/http.c Thu Aug 15 11:30:42 2002 @@ -133,8 +133,6 @@ int remove = 0; int forbidden = 0; StoreEntry *pe; - if (!EBIT_TEST(e->flags, KEY_PRIVATE)) - return; switch (status) { case HTTP_OK: case HTTP_NON_AUTHORITATIVE_INFORMATION: @@ -166,14 +164,16 @@ } if (!remove && !forbidden) return; - assert(e->mem_obj); - if (e->mem_obj->request) - pe = storeGetPublicByRequest(e->mem_obj->request); - else - pe = storeGetPublic(e->mem_obj->url, e->mem_obj->method); - if (pe != NULL) { - assert(e != pe); - storeRelease(pe); + if (EBIT_TEST(e->flags, KEY_PRIVATE)) { + assert(e->mem_obj); + if (e->mem_obj->request) + pe = storeGetPublicByRequest(e->mem_obj->request); + else + pe = storeGetPublic(e->mem_obj->url, e->mem_obj->method); + if (pe != NULL) { + assert(e != pe); + storeRelease(pe); + } } /* * Also remove any cached HEAD response in case the object has @@ -183,8 +183,7 @@ pe = storeGetPublicByRequestMethod(e->mem_obj->request, METHOD_HEAD); else pe = storeGetPublic(e->mem_obj->url, METHOD_HEAD); - if (pe != NULL) { - assert(e != pe); + if (pe != NULL && e != pe) { storeRelease(pe); } if (forbidden) @@ -325,7 +324,7 @@ * Returns false if the variance cannot be stored */ const char * -httpMakeVaryMark(request_t * request, HttpReply * reply) +httpMakeVaryMark(const request_t * request, HttpReply * reply) { String vary, hdr; const char *pos = NULL; @@ -426,8 +425,6 @@ storeTimestampsSet(entry); /* Check if object is cacheable or not based on reply code */ debug(11, 3) ("httpProcessReplyHeader: HTTP CODE: %d\n", reply->sline.status); - if (neighbors_do_private_keys) - httpMaybeRemovePublic(entry, reply->sline.status); switch (httpCachableReply(httpState)) { case 1: if (httpHeaderHas(&reply->header, HDR_VARY) @@ -463,6 +460,8 @@ else if (EBIT_TEST(reply->cache_control->mask, CC_MUST_REVALIDATE)) EBIT_SET(entry->flags, ENTRY_REVALIDATE); } + if (neighbors_do_private_keys) + httpMaybeRemovePublic(entry, reply->sline.status); if (httpState->flags.keepalive) if (httpState->peer) httpState->peer->stats.n_keepalives_sent++; @@ -737,6 +736,13 @@ /* append our IMS header */ if (request->lastmod > -1 && request->method == METHOD_GET) httpHeaderPutTime(hdr_out, HDR_IF_MODIFIED_SINCE, request->lastmod); + if (request->etag) + httpHeaderPutStr(hdr_out, HDR_IF_NONE_MATCH, request->etag); + else if (request->etags) { + int i; + for (i = 0; i < request->etags->count; i++) + httpHeaderPutStr(hdr_out, HDR_IF_NONE_MATCH, request->etags->items[i]); + } strConnection = httpHeaderGetList(hdr_in, HDR_CONNECTION); while ((e = httpHeaderGetEntry(hdr_in, &pos))) { Index: squid/src/protos.h diff -u squid/src/protos.h:1.59 squid/src/protos.h:1.29.2.13 --- squid/src/protos.h:1.59 Sat Jul 20 05:33:16 2002 +++ squid/src/protos.h Thu Aug 15 11:30:43 2002 @@ -313,11 +313,7 @@ extern int httpAnonHdrDenied(http_hdr_type hdr_id); extern void httpBuildRequestHeader(request_t *, request_t *, StoreEntry *, HttpHeader *, int, http_state_flags); extern void httpBuildVersion(http_version_t * version, unsigned int major, unsigned int minor); -extern const char *httpMakeVaryMark(request_t * request, HttpReply * reply); - -/* ETag */ -extern int etagParseInit(ETag * etag, const char *str); -extern int etagIsEqual(const ETag * tag1, const ETag * tag2); +extern const char *httpMakeVaryMark(const request_t * request, HttpReply * reply); /* Http Status Line */ /* init/clean */ @@ -444,7 +440,6 @@ extern time_t httpHeaderGetTime(const HttpHeader * hdr, http_hdr_type id); extern TimeOrTag httpHeaderGetTimeOrTag(const HttpHeader * hdr, http_hdr_type id); extern HttpHdrCc *httpHeaderGetCc(const HttpHeader * hdr); -extern ETag httpHeaderGetETag(const HttpHeader * hdr, http_hdr_type id); extern HttpHdrRange *httpHeaderGetRange(const HttpHeader * hdr); extern HttpHdrContRange *httpHeaderGetContRange(const HttpHeader * hdr); extern const char *httpHeaderGetStr(const HttpHeader * hdr, http_hdr_type id); @@ -1312,9 +1307,6 @@ */ extern StatCounters *snmpStatGet(int); -/* Vary support functions */ -int varyEvaluateMatch(StoreEntry * entry, request_t * req); - /* CygWin & Windows NT Port */ /* win32.c */ #if defined(_SQUID_MSWIN_) || defined(_SQUID_CYGWIN_) @@ -1335,4 +1327,8 @@ extern void externalAclInit(void); extern void externalAclShutdown(void); +void storeLocateVaryDone(VaryData *data); +void storeLocateVary(StoreEntry * e, int offset, const char *vary_data, STLVCB *callback, void *cbdata); +void storeAddVary(const char *url, const char *log_url, const method_t method, const cache_key *key, const char *etag, const char *vary, const char *vary_headers); + #endif /* SQUID_PROTOS_H */ Index: squid/src/store.c diff -u squid/src/store.c:1.17 squid/src/store.c:1.12.14.23 --- squid/src/store.c:1.17 Thu Aug 15 11:14:04 2002 +++ squid/src/store.c Thu Aug 15 15:28:18 2002 @@ -329,6 +329,13 @@ StoreEntry * storeGetPublicByRequestMethod(request_t * req, const method_t method) { + if (req->vary) { + /* Varying objects... */ + if (req->vary->key) + return storeGet(storeKeyScan(req->vary->key)); + else + return NULL; + } return storeGet(storeKeyPublicByRequestMethod(req, method)); } @@ -374,6 +381,440 @@ storeHashInsert(e, newkey); } +typedef struct { + StoreEntry *oe; + StoreEntry *e; + store_client *sc; + char *url; + char *key; + char *vary_headers; + char *etag; + int offset; + int seen_offset; + char *buf; + size_t buf_size; + int done:1; + struct { + char *key; + char *etag; + int this_key:1; + int key_used:1; + int ignore:1; + } current; +} AddVaryState; +CBDATA_TYPE(AddVaryState); +static void free_AddVaryState(void *data) +{ + AddVaryState *state = data; + debug(11, 2) ("free_AddVaryState: %p\n", data); + if (!state->done && state->key) { + storeAppendPrintf(state->e, "Key: %s\n", state->key); + if (state->etag) + storeAppendPrintf(state->e, "ETag: %s\n", state->etag); + storeAppendPrintf(state->e, "VaryData: %s\n", state->vary_headers); + } + storeBufferFlush(state->e); + storeTimestampsSet(state->e); + storeComplete(state->e); + storeTimestampsSet(state->e); + storeUnlockObject(state->e); + state->e = NULL; + if (state->oe) { + storeUnlockObject(state->oe); + state->oe = NULL; + } + safe_free(state->url); + safe_free(state->key); + safe_free(state->vary_headers); + safe_free(state->etag); + safe_free(state->current.key); + safe_free(state->current.etag); + if (state->buf) { + memFreeBuf(state->buf_size, state->buf); + state->buf = NULL; + } +} + +static int inline +strmatchbeg(const char *search, const char *match, int maxlen) +{ + int mlen = strlen(match); + if (maxlen < mlen) + return -1; + return strncmp(search, match, mlen); +} + +static int inline +strmatch(const char *search, const char *match, int maxlen) +{ + int mlen = strlen(match); + if (maxlen < mlen) + return -1; + return strncmp(search, match, maxlen); +} + +static void storeAddVaryFlush(AddVaryState *state) +{ + if (state->current.ignore || state->current.key_used) { + /* do nothing */ + } else if (state->current.this_key) { + if (state->current.key) + storeAppendPrintf(state->e, "Key: %s\n", state->current.key); + else + storeAppendPrintf(state->e, "Key: %s\n", state->key); + if (state->etag) + storeAppendPrintf(state->e, "ETag: %s\n", state->etag); + storeAppendPrintf(state->e, "VaryData: %s\n", state->vary_headers); + state->done = 1; + state->current.key_used = 1; + } else if (state->current.key) { + storeAppendPrintf(state->e, "Key: %s\n", state->current.key); + safe_free(state->current.key); + if (state->current.etag) { + storeAppendPrintf(state->e, "ETag: %s\n", state->current.etag); + safe_free(state->current.etag); + } + state->current.key_used = 1; + } +} + +static void +storeAddVaryReadOld(void *data, char *buf, ssize_t size) +{ + AddVaryState *state = data; + int l = size; + char *e; + char *p = buf; + debug(11, 3) ("storeAddVaryReadOld: %p offset=%d seen_offset=%d size=%d\n", data, state->offset, state->seen_offset, size); + if (size <= 0) { + storeUnregister(state->sc, state->oe, state); + state->sc = NULL; + cbdataFree(state); + debug(11, 2) ("storeAddVaryReadOld: DONE\n"); + return; + } + state->seen_offset = state->offset + size; + while ((e = memchr(p, '\n', l)) != NULL) { + int l2; + char *p2; + if (strmatchbeg(p, "Key: ", l) == 0) { + /* key field */ + p2 = p + 5; + l2 = e - p2; + if (state->current.this_key) { + storeAddVaryFlush(state); + } + safe_free(state->current.key); + safe_free(state->current.etag); + memset(&state->current, 0, sizeof(state->current)); + state->current.key = xmalloc(l2+1); + memcpy(state->current.key, p2, l2); + state->current.key[l2] = '\0'; + if (state->key) { + if (strcmp(state->current.key, state->key) == 0) { + state->current.this_key = 1; + } + } + debug(11, 3) ("storeAddVaryReadOld: Key: %s%s\n", state->current.key, state->current.this_key ? " (THIS)" : ""); + } else if (strmatchbeg(p, "ETag: ", l) == 0) { + /* etag field */ + p2 = p + 6; + l2 = e - p2; + safe_free(state->current.etag); + state->current.etag = xmalloc(l2+1); + memcpy(state->current.etag, p2, l2); + state->current.etag[l2] = '\0'; + if (state->etag && strcmp(state->current.etag, state->etag) == 0) { + if (!state->key) { + state->current.this_key = 1; + } else { + const cache_key *oldkey = storeKeyScan(state->current.key); + if (strmatch(p2, state->key, l) != 0) { + StoreEntry *old_e = storeGet(oldkey); + if (old_e) + storeRelease(old_e); + safe_free(state->current.key); + state->current.key = xstrdup(state->key); + state->current.this_key = 1; + } + } + } else if (state->current.this_key) { + state->current.ignore = 1; + } + debug(11, 2) ("storeAddVaryReadOld: ETag: %s%s%s\n", state->current.etag, state->current.this_key ? " (THIS)" : "", state->current.ignore ? " (IGNORE)" : ""); + } else if (!state->current.ignore && strmatchbeg(p, "VaryData: ", l) == 0) { + /* vary field */ + p2 = p + 10; + l2 = e - p2; + storeAddVaryFlush(state); + if (strmatch(p2, state->vary_headers, l2) != 0) { + storeAppend(state->e, p, e - p + 1); + debug(11, 3) ("storeAddVaryReadOld: %s\n", p); + } + } + e += 1; + l -= e - p; + p = e; + if (l == 0) + break; + assert(l>0); + assert(p < (buf+size)); + } + if (p == state->buf && size == state->buf_size) { + /* Oops.. the buffer size is not sufficient. Grow */ + if (state->buf_size < 65536) { + debug(11, 2) ("storeAddVaryReadOld: Increasing entry buffer size to %d\n", state->buf_size * 2); + state->buf = memReallocBuf(state->buf, state->buf_size * 2, &state->buf_size); + } else { + /* This does not look good. Bail out. This should match the size <= 0 case above */ + debug(11, 1) ("storeAddVaryReadOld: Buffer very large and still can't fit the data.. bailing out\n"); + storeUnregister(state->sc, state->oe, state); + state->sc = NULL; + cbdataFree(state); + return; + } + } + state->offset += p - buf; + debug(11, 3) ("storeAddVaryReadOld: %p offset=%d seen_offset=%d\n", data, state->offset, state->seen_offset); + storeClientCopy(state->sc, state->oe, + state->seen_offset, + state->offset, + state->buf_size, + state->buf, + storeAddVaryReadOld, + state); +} + +/* + * Adds/updates a Vary record. + * For updates only one of key or etag needs to be specified + * At leas one of key or etag must be specified, preferably both. + */ +void +storeAddVary(const char *url, const char *log_url, const method_t method, const cache_key *key, const char *etag, const char *vary, const char *vary_headers) +{ + AddVaryState *state; + http_version_t version; + request_flags flags = null_request_flags; + CBDATA_INIT_TYPE_FREECB(AddVaryState, free_AddVaryState); + state = cbdataAlloc(AddVaryState); + state->url = xstrdup(url); + if (key) + state->key = xstrdup(storeKeyText(key)); + state->vary_headers = xstrdup(vary_headers); + if (etag) + state->etag = xstrdup(etag); + state->oe = storeGetPublic(url, method); + debug(11, 2) ("storeAddVary: %s (%s) %s %s\n", + state->url, state->key, state->vary_headers, state->etag); + if (state->oe) + storeLockObject(state->oe); + flags.cachable = 1; + state->e = storeCreateEntry(url, log_url, flags, method); + httpBuildVersion(&version, 1, 0); + httpReplySetHeaders(state->e->mem_obj->reply, version, HTTP_OK, "Internal marker object", "x-squid-internal/vary", -1, -1, squid_curtime + 100000); + httpHeaderPutStr(&state->e->mem_obj->reply->header, HDR_VARY, vary); + storeSetPublicKey(state->e); + httpReplySwapOut(state->e->mem_obj->reply, state->e); + if (state->oe) { + /* Here we need to tack on the old etag/vary information, and we should + * merge, clean up etc + * + * Suggestion: + * swap in the old file, looking for ETag, Key and VaryData. If a match is + * found then + * - on ETag, update the key, and expire the old object if different + * - on Key, drop the old data if ETag is different, else nothing + * - on VaryData, remove the line if a different key. If this makes + * the searched key "empty" then expire it and remove it from the + * map + * - VaryData is added last in the Key record it corresponds to (after + * modifications above) + */ + /* Swap in the dummy Vary object */ + if (!state->oe->mem_obj) { + storeCreateMemObject(state->oe, state->url, log_url); + state->oe->mem_obj->method = method; + } + state->sc = storeClientListAdd(state->oe, state); + state->buf = memAllocBuf(4096, &state->buf_size); + debug(11, 3) ("storeAddVary: %p\n", state); + storeClientCopy(state->sc, state->oe, 0, 0, + state->buf_size, + state->buf, + storeAddVaryReadOld, + state); + return; + } else { + cbdataFree(state); + } +} + +static MemPool *VaryData_pool = NULL; + +void storeLocateVaryDone(VaryData *data) +{ + int i; + safe_free(data->key); + data->etag = NULL; /* points to an entry in etags */ + for (i=0 ; i < data->etags.count; i++) { + safe_free(data->etags.items[i]); + } + arrayClean(&data->etags); + memPoolFree(VaryData_pool, data); +} + +typedef struct { + VaryData *data; + STLVCB *callback; + void *callback_data; + StoreEntry *e; + store_client *sc; + char *buf; + size_t buf_size; + char *vary_data; + int offset, seen_offset; + struct { + char *key; + char *etag; + } current; +} LocateVaryState; +CBDATA_TYPE(LocateVaryState); + +static void +storeLocateVaryCallback(LocateVaryState *state) +{ + if (cbdataValid(state->callback_data)) { + VaryData *data = state->data; + if (data->key || data->etags.count) { + state->callback(data, state->callback_data); + state->data = NULL; /* now owned by the caller */ + } else { + state->callback(NULL, state->callback_data); + } + } + cbdataUnlock(state->callback_data); + if (state->data) { + storeLocateVaryDone(state->data); + state->data = NULL; + } + state->current.etag = NULL; /* shared by data->entries[x] */ + safe_free(state->vary_data); + safe_free(state->current.key); + if (state->sc) { + storeUnregister(state->sc, state->e, state); + state->sc = NULL; + } + if (state->e) { + storeUnlockObject(state->e); + state->e = NULL; + } + if (state->buf) { + memFreeBuf(state->buf_size, state->buf); + state->buf = NULL; + } + cbdataFree(state); + debug(11, 2) ("storeLocateVary: DONE\n"); +} + +static void +storeLocateVaryRead(void *data, char *buf, ssize_t size) +{ + LocateVaryState *state = data; + char *e; + char *p = buf; + int l = size; + debug(11, 3) ("storeLocateVaryRead: %s %p offset=%d seen_offset=%d size=%d\n", state->vary_data, data, state->offset, state->seen_offset, size); + if (size <= 0) { + storeLocateVaryCallback(state); + return; + } + state->seen_offset = state->offset + size; + while ((e = memchr(p, '\n', l)) != NULL) { + int l2; + char *p2; + if (strmatchbeg(p, "Key: ", l) == 0) { + /* key field */ + p2 = p + 5; + l2 = e - p2; + safe_free(state->current.key); + state->current.etag = NULL; + safe_free(state->current.etag); + memset(&state->current, 0, sizeof(state->current)); + state->current.key = xmalloc(l2+1); + memcpy(state->current.key, p2, l2); + state->current.key[l2] = '\0'; + debug(11, 3) ("storeLocateVaryRead: Key: %s\n", state->current.key); + } else if (strmatchbeg(p, "ETag: ", l) == 0) { + /* etag field */ + char *etag; + p2 = p + 6; + l2 = e - p2; + etag = xmalloc(l2+1); + memcpy(etag, p2, l2); + etag[l2] = '\0'; + state->current.etag = etag; + arrayAppend(&state->data->etags, etag); + debug(11, 3) ("storeLocateVaryRead: ETag: %s\n", etag); + } else if (strmatchbeg(p, "VaryData: ", l) == 0) { + /* vary field */ + p2 = p + 10; + l2 = e - p2; + if (strmatch(p2, state->vary_data, l2) == 0) { + /* A matching vary header found */ + safe_free(state->data->key); + state->data->key = xstrdup(state->current.key); + state->data->etag = state->current.etag; + debug(11, 2) ("storeLocateVaryRead: MATCH! %s %s\n", state->current.key, state->current.etag); + } + } + e += 1; + l -= e - p; + p = e; + if (l == 0) + break; + assert(l>0); + assert(p < (buf+size)); + } + state->offset += p - buf; + if (p == state->buf && size == state->buf_size) { + /* Oops.. the buffer size is not sufficient. Grow */ + if (state->buf_size < 65536) { + debug(11, 2) ("storeLocateVaryRead: Increasing entry buffer size to %d\n", state->buf_size * 2); + state->buf = memReallocBuf(state->buf, state->buf_size * 2, &state->buf_size); + } else { + /* This does not look good. Bail out. This should match the size <= 0 case above */ + debug(11, 1) ("storeLocateVaryRead: Buffer very large and still can't fit the data.. bailing out\n"); + storeLocateVaryCallback(state); + return; + } + } + debug(11, 3) ("storeLocateVaryRead: %p offset=%d seen_offset=%d\n", data, state->offset, state->seen_offset); + storeClientCopy(state->sc, state->e, state->offset, state->seen_offset, state->buf_size, state->buf, storeLocateVaryRead, state); +} + +void +storeLocateVary(StoreEntry * e, int offset, const char *vary_data, STLVCB *callback, void *cbdata) +{ + LocateVaryState *state; + debug(11, 2) ("storeLocateVary: %s\n", vary_data); + CBDATA_INIT_TYPE(LocateVaryState); + if (!VaryData_pool) + VaryData_pool = memPoolCreate("VaryData", sizeof(VaryData)); + state = cbdataAlloc(LocateVaryState); + state->vary_data = xstrdup(vary_data); + state->data = memPoolAlloc(VaryData_pool); + state->e = e; + storeLockObject(state->e); + state->callback_data = cbdata; + cbdataLock(cbdata); + state->callback = callback; + state->buf = memAllocBuf(4096, &state->buf_size); + state->sc = storeClientListAdd(state->e, state); + state->offset = state->seen_offset = offset; + storeClientCopy(state->sc, state->e, state->offset, state->seen_offset, state->buf_size, state->buf, storeLocateVaryRead, state); +} + void storeSetPublicKey(StoreEntry * e) { @@ -422,35 +863,27 @@ request->vary_headers = xstrdup(vary); } } - if (mem->vary_headers && !storeGetPublic(mem->url, mem->method)) { - /* Create "vary" base object */ - http_version_t version; - String vary; - pe = storeCreateEntry(mem->url, mem->log_url, request->flags, request->method); - httpBuildVersion(&version, 1, 0); - httpReplySetHeaders(pe->mem_obj->reply, version, HTTP_OK, "Internal marker object", "x-squid-internal/vary", -1, -1, squid_curtime + 100000); - vary = httpHeaderGetList(&mem->reply->header, HDR_VARY); - if (strBuf(vary)) { - httpHeaderPutStr(&pe->mem_obj->reply->header, HDR_VARY, strBuf(vary)); - stringClean(&vary); - } + newkey = storeKeyPublicByRequest(mem->request); + if (mem->vary_headers) { + String vary = StringNull; + String varyhdr; + varyhdr = httpHeaderGetList(&mem->reply->header, HDR_VARY); + if (strBuf(varyhdr)) + strListAdd(&vary, strBuf(varyhdr), ','); + stringClean(&varyhdr); #if X_ACCELERATOR_VARY - vary = httpHeaderGetList(&mem->reply->header, HDR_X_ACCELERATOR_VARY); - if (strBuf(vary)) { - httpHeaderPutStr(&pe->mem_obj->reply->header, HDR_X_ACCELERATOR_VARY, strBuf(vary)); - stringClean(&vary); - } + /* This needs to match the order in http.c:httpMakeVaryMark */ + varyhdr = httpHeaderGetList(&mem->reply->header, HDR_X_ACCELERATOR_VARY); + if (strBuf(varyhdr)) + strListAdd(&vary, strBuf(varyhdr), ','); + stringClean(&varyhdr); #endif - storeSetPublicKey(pe); - httpReplySwapOut(pe->mem_obj->reply, pe); - storeBufferFlush(pe); - storeTimestampsSet(pe); - storeComplete(pe); - storeUnlockObject(pe); + storeAddVary(mem->url, mem->log_url, mem->method, newkey, httpHeaderGetStr(&mem->reply->header, HDR_ETAG), strBuf(vary), mem->vary_headers); + stringClean(&vary); } - newkey = storeKeyPublicByRequest(mem->request); - } else + } else { newkey = storeKeyPublic(mem->url, mem->method); + } if ((e2 = (StoreEntry *) hash_lookup(store_table, newkey))) { debug(20, 3) ("storeSetPublicKey: Making old '%s' private.\n", mem->url); storeSetPrivateKey(e2); Index: squid/src/structs.h diff -u squid/src/structs.h:1.62 squid/src/structs.h:1.33.4.11 --- squid/src/structs.h:1.62 Fri Aug 9 14:46:02 2002 +++ squid/src/structs.h Thu Aug 15 11:30:44 2002 @@ -884,7 +884,7 @@ /* some fields can hold either time or etag specs (e.g. If-Range) */ struct _TimeOrTag { - ETag tag; /* entity tag */ + const char *tag; /* entity tag */ time_t time; int valid; /* true if struct is usable */ }; @@ -1678,7 +1678,11 @@ err_type err_type; char *peer_login; /* Configured peer login:password */ time_t lastmod; /* Used on refreshes */ - const char *vary_headers; /* Used when varying entities are detected. Changes how the store key is calculated */ + char *vary_headers; /* Used when varying entities are detected. Changes how the store key is calculated */ + VaryData *vary; + Array *etags; /* possible known entity tags (Vary MISS) */ + char *etag; /* current entity tag, cache validation */ + unsigned int done_etag:1; /* We have done clientProcessETag on this, don't attempt it again */ }; struct _cachemgr_passwd { @@ -2164,4 +2168,10 @@ void (*dump) (StoreEntry * e, const char *option, SwapDir * sd); }; +struct _VaryData { + cache_key *key; + char *etag; + Array etags; +}; + #endif /* SQUID_STRUCTS_H */ Index: squid/src/typedefs.h diff -u squid/src/typedefs.h:1.27 squid/src/typedefs.h:1.21.4.4 --- squid/src/typedefs.h:1.27 Sun Jun 23 06:38:04 2002 +++ squid/src/typedefs.h Thu Aug 15 11:30:45 2002 @@ -354,4 +354,7 @@ typedef struct _external_acl external_acl; typedef struct _external_acl_entry external_acl_entry; +typedef struct _VaryData VaryData; +typedef void STLVCB(VaryData *vary, void *cbdata); + #endif /* SQUID_TYPEDEFS_H */