/*
- * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
#include "Store.h"
#include "StrList.h"
#include "tools.h"
-#include "URL.h"
#if USE_AUTH
#include "auth/UserRequest.h"
#endif
/* Local functions */
extern "C" CSS clientReplyStatus;
-ErrorState *clientBuildError(err_type, Http::StatusCode, char const *, Ip::Address &, HttpRequest *);
+ErrorState *clientBuildError(err_type, Http::StatusCode, char const *, const ConnStateData *, HttpRequest *, const AccessLogEntry::Pointer &);
/* privates */
clientReplyContext::clientReplyContext(ClientHttpRequest *clientContext) :
purgeStatus(Http::scNone),
- lookingforstore(0),
http(cbdataReference(clientContext)),
headers_sz(0),
sc(NULL),
old_reqsize(0),
reqsize(0),
reqofs(0),
-#if USE_CACHE_DIGESTS
- lookup_type(NULL),
-#endif
ourNode(NULL),
reply(NULL),
old_entry(NULL),
old_sc(NULL),
+ old_lastmod(-1),
deleting(false),
collapsedRevalidation(crNone)
{
void
clientReplyContext::setReplyToError(
err_type err, Http::StatusCode status, const HttpRequestMethod& method, char const *uri,
- Ip::Address &addr, HttpRequest * failedrequest, const char *unparsedrequest,
+ const ConnStateData *conn, HttpRequest *failedrequest, const char *unparsedrequest,
#if USE_AUTH
Auth::UserRequest::Pointer auth_user_request
#else
#endif
)
{
- ErrorState *errstate = clientBuildError(err, status, uri, addr, failedrequest);
+ auto errstate = clientBuildError(err, status, uri, conn, failedrequest, http->al);
if (unparsedrequest)
errstate->request_hdrs = xstrdup(unparsedrequest);
debugs(88, 3, "clientReplyContext::saveState: saving store context");
old_entry = http->storeEntry();
old_sc = sc;
+ old_lastmod = http->request->lastmod;
+ old_etag = http->request->etag;
old_reqsize = reqsize;
tempBuffer.offset = reqofs;
/* Prevent accessing the now saved entries */
sc = old_sc;
reqsize = old_reqsize;
reqofs = tempBuffer.offset;
+ http->request->lastmod = old_lastmod;
+ http->request->etag = old_etag;
/* Prevent accessed the old saved entries */
old_entry = NULL;
old_sc = NULL;
+ old_lastmod = -1;
+ old_etag.clean();
old_reqsize = 0;
tempBuffer.offset = 0;
}
return;
}
- http->logType = LOG_TCP_REFRESH;
+ http->logType.update(LOG_TCP_REFRESH);
http->request->flags.refresh = true;
#if STORE_CLIENT_LIST_DEBUG
/* Prevent a race with the store client memory free routines
/* Prepare to make a new temporary request */
saveState();
+ // TODO: Consider also allowing regular (non-collapsed) revalidation hits.
// TODO: support collapsed revalidation for Vary-controlled entries
- const bool collapsingAllowed = Config.onoff.collapsed_forwarding &&
- !Store::Root().smpAware() &&
- http->request->vary_headers.isEmpty();
+ bool collapsingAllowed = Config.onoff.collapsed_forwarding &&
+ !Store::Controller::SmpAware() &&
+ http->request->vary_headers.isEmpty();
StoreEntry *entry = nullptr;
if (collapsingAllowed) {
- if ((entry = storeGetPublicByRequest(http->request, ksRevalidation)))
- entry->lock("clientReplyContext::processExpired#alreadyRevalidating");
+ if (const auto e = storeGetPublicByRequest(http->request, ksRevalidation)) {
+ if (e->hittingRequiresCollapsing() && startCollapsingOn(*e, true)) {
+ entry = e;
+ entry->lock("clientReplyContext::processExpired#alreadyRevalidating");
+ } else {
+ e->abandon(__FUNCTION__);
+ // assume mayInitiateCollapsing() would fail too
+ collapsingAllowed = false;
+ }
+ }
}
if (entry) {
+ entry->ensureMemObject(url, http->log_uri, http->request->method);
debugs(88, 5, "collapsed on existing revalidation entry: " << *entry);
collapsedRevalidation = crSlave;
} else {
http->log_uri, http->request->flags, http->request->method);
/* NOTE, don't call StoreEntry->lock(), storeCreateEntry() does it */
- if (collapsingAllowed) {
+ if (collapsingAllowed && mayInitiateCollapsing() &&
+ Store::Root().allowCollapsing(entry, http->request->flags, http->request->method)) {
debugs(88, 5, "allow other revalidation requests to collapse on " << *entry);
- Store::Root().allowCollapsing(entry, http->request->flags,
- http->request->method);
collapsedRevalidation = crInitiator;
} else {
collapsedRevalidation = crNone;
{
StoreIOBuffer tempresult;
removeStoreReference(&old_sc, &old_entry);
+
+ if (collapsedRevalidation)
+ http->storeEntry()->clearPublicKeyScope();
+
/* here the data to send is the data we just received */
tempBuffer.offset = 0;
old_reqsize = 0;
if (result.flags.error && !EBIT_TEST(http->storeEntry()->flags, ENTRY_ABORTED))
return;
- if (collapsedRevalidation == crSlave && EBIT_TEST(http->storeEntry()->flags, KEY_PRIVATE)) {
- debugs(88, 3, "CF slave hit private " << *http->storeEntry() << ". MISS");
+ if (collapsedRevalidation == crSlave && !http->storeEntry()->mayStartHitting()) {
+ debugs(88, 3, "CF slave hit private non-shareable " << *http->storeEntry() << ". MISS");
// restore context to meet processMiss() expectations
restoreState();
- http->logType = LOG_TCP_MISS;
+ http->logType.update(LOG_TCP_MISS);
processMiss();
return;
}
/* update size of the request */
reqsize = result.length + reqofs;
- const Http::StatusCode status = http->storeEntry()->getReply()->sline.status();
-
// request to origin was aborted
if (EBIT_TEST(http->storeEntry()->flags, ENTRY_ABORTED)) {
debugs(88, 3, "request to origin aborted '" << http->storeEntry()->url() << "', sending old entry to client");
- http->logType = LOG_TCP_REFRESH_FAIL_OLD;
+ http->logType.update(LOG_TCP_REFRESH_FAIL_OLD);
sendClientOldEntry();
+ return;
}
- const HttpReply *old_rep = old_entry->getReply();
+ const auto oldStatus = old_entry->mem().freshestReply().sline.status();
+ const auto &new_rep = http->storeEntry()->mem().freshestReply();
+ const auto status = new_rep.sline.status();
+
+ // XXX: Disregard stale incomplete (i.e. still being written) borrowed (i.e.
+ // not caused by our request) IMS responses. That new_rep may be very old!
// origin replied 304
if (status == Http::scNotModified) {
- http->logType = LOG_TCP_REFRESH_UNMODIFIED;
+ http->logType.update(LOG_TCP_REFRESH_UNMODIFIED);
http->request->flags.staleIfHit = false; // old_entry is no longer stale
- // update headers on existing entry
+ // TODO: The update may not be instantaneous. Should we wait for its
+ // completion to avoid spawning too much client-disassociated work?
Store::Root().updateOnNotModified(old_entry, *http->storeEntry());
// if client sent IMS
-
if (http->request->flags.ims && !old_entry->modifiedSince(http->request->ims, http->request->imslen)) {
// forward the 304 from origin
- debugs(88, 3, "origin replied 304, revalidating existing entry and forwarding 304 to client");
+ debugs(88, 3, "origin replied 304, revalidated existing entry and forwarding 304 to client");
sendClientUpstreamResponse();
- } else {
- // send existing entry, it's still valid
- debugs(88, 3, "origin replied 304, revalidating existing entry and sending " <<
- old_rep->sline.status() << " to client");
- sendClientOldEntry();
+ return;
}
+
+ // send existing entry, it's still valid
+ debugs(88, 3, "origin replied 304, revalidated existing entry and sending " << oldStatus << " to client");
+ sendClientOldEntry();
+ return;
}
// origin replied with a non-error code
- else if (status > Http::scNone && status < Http::scInternalServerError) {
- const HttpReply *new_rep = http->storeEntry()->getReply();
+ if (status > Http::scNone && status < Http::scInternalServerError) {
// RFC 7234 section 4: a cache MUST use the most recent response
// (as determined by the Date header field)
- if (new_rep->olderThan(old_rep)) {
+ if (new_rep.olderThan(&old_entry->mem().freshestReply())) {
http->logType.err.ignored = true;
- debugs(88, 3, "origin replied " << status <<
- " but with an older date header, sending old entry (" <<
- old_rep->sline.status() << ") to client");
+ debugs(88, 3, "origin replied " << status << " but with an older date header, sending old entry (" << oldStatus << ") to client");
sendClientOldEntry();
- } else {
- http->logType = LOG_TCP_REFRESH_MODIFIED;
- debugs(88, 3, "origin replied " << status <<
- ", replacing existing entry and forwarding to client");
-
- if (collapsedRevalidation)
- http->storeEntry()->clearPublicKeyScope();
-
- sendClientUpstreamResponse();
+ return;
}
+
+ http->logType.update(LOG_TCP_REFRESH_MODIFIED);
+ debugs(88, 3, "origin replied " << status << ", forwarding to client");
+ sendClientUpstreamResponse();
+ return;
}
// origin replied with an error
- else if (http->request->flags.failOnValidationError) {
- http->logType = LOG_TCP_REFRESH_FAIL_ERR;
- debugs(88, 3, "origin replied with error " << status <<
- ", forwarding to client due to fail_on_validation_err");
+ if (http->request->flags.failOnValidationError) {
+ http->logType.update(LOG_TCP_REFRESH_FAIL_ERR);
+ debugs(88, 3, "origin replied with error " << status << ", forwarding to client due to fail_on_validation_err");
sendClientUpstreamResponse();
- } else {
- // ignore and let client have old entry
- http->logType = LOG_TCP_REFRESH_FAIL_OLD;
- debugs(88, 3, "origin replied with error " <<
- status << ", sending old entry (" << old_rep->sline.status() << ") to client");
- sendClientOldEntry();
+ return;
}
+
+ // ignore and let client have old entry
+ http->logType.update(LOG_TCP_REFRESH_FAIL_OLD);
+ debugs(88, 3, "origin replied with error " << status << ", sending old entry (" << oldStatus << ") to client");
+ sendClientOldEntry();
}
SQUIDCEXTERN CSR clientGetMoreData;
} else if (result.flags.error) {
/* swap in failure */
debugs(88, 3, "clientCacheHit: swapin failure for " << http->uri);
- http->logType = LOG_TCP_SWAPFAIL_MISS;
+ http->logType.update(LOG_TCP_SWAPFAIL_MISS);
removeClientStoreReference(&sc, http);
processMiss();
return;
}
- // The previously identified hit suddenly became unsharable!
+ // The previously identified hit suddenly became unshareable!
// This is common for collapsed forwarding slaves but might also
// happen to regular hits because we are called asynchronously.
- if (EBIT_TEST(e->flags, KEY_PRIVATE)) {
- debugs(88, 3, "unsharable " << *e << ". MISS");
- http->logType = LOG_TCP_MISS;
+ if (!e->mayStartHitting()) {
+ debugs(88, 3, "unshareable " << *e << ". MISS");
+ http->logType.update(LOG_TCP_MISS);
processMiss();
return;
}
* object
*/
/* treat as a miss */
- http->logType = LOG_TCP_MISS;
+ http->logType.update(LOG_TCP_MISS);
processMiss();
return;
}
if (http->request->storeId().cmp(e->mem_obj->storeId()) != 0) {
debugs(33, DBG_IMPORTANT, "clientProcessHit: URL mismatch, '" << e->mem_obj->storeId() << "' != '" << http->request->storeId() << "'");
- http->logType = LOG_TCP_MISS; // we lack a more precise LOG_*_MISS code
+ http->logType.update(LOG_TCP_MISS); // we lack a more precise LOG_*_MISS code
processMiss();
return;
}
case VARY_CANCEL:
/* varyEvaluateMatch found a object loop. Process as miss */
debugs(88, DBG_IMPORTANT, "clientProcessHit: Vary object loop!");
- http->logType = LOG_TCP_MISS; // we lack a more precise LOG_*_MISS code
+ http->logType.update(LOG_TCP_MISS); // we lack a more precise LOG_*_MISS code
processMiss();
return;
}
if (e->checkNegativeHit() && !r->flags.noCacheHack()) {
debugs(88, 5, "negative-HIT");
- http->logType = LOG_TCP_NEGATIVE_HIT;
+ http->logType.update(LOG_TCP_NEGATIVE_HIT);
sendMoreData(result);
return;
} else if (blockedHit()) {
debugs(88, 5, "send_hit forces a MISS");
- http->logType = LOG_TCP_MISS;
+ http->logType.update(LOG_TCP_MISS);
processMiss();
return;
} else if (!http->flags.internal && refreshCheckHTTP(e, r)) {
* modification time.
* XXX: BUG 1890 objects without Date do not get one added.
*/
- http->logType = LOG_TCP_MISS;
+ http->logType.update(LOG_TCP_MISS);
processMiss();
} else if (r->flags.noCache) {
debugs(88, 3, "validate HIT object? NO. Client sent CC:no-cache. Do CLIENT_REFRESH_MISS");
* This did not match a refresh pattern that overrides no-cache
* we should honour the client no-cache header.
*/
- http->logType = LOG_TCP_CLIENT_REFRESH_MISS;
+ http->logType.update(LOG_TCP_CLIENT_REFRESH_MISS);
processMiss();
- } else if (r->url.getScheme() == AnyP::PROTO_HTTP) {
+ } else if (r->url.getScheme() == AnyP::PROTO_HTTP || r->url.getScheme() == AnyP::PROTO_HTTPS) {
debugs(88, 3, "validate HIT object? YES.");
/*
* Object needs to be revalidated
* We don't know how to re-validate other protocols. Handle
* them as if the object has expired.
*/
- http->logType = LOG_TCP_MISS;
+ http->logType.update(LOG_TCP_MISS);
processMiss();
}
return;
#if USE_DELAY_POOLS
if (e->store_status != STORE_OK)
- http->logType = LOG_TCP_MISS;
+ http->logType.update(LOG_TCP_MISS);
else
#endif
if (e->mem_status == IN_MEMORY)
- http->logType = LOG_TCP_MEM_HIT;
+ http->logType.update(LOG_TCP_MEM_HIT);
else if (Config.onoff.offline)
- http->logType = LOG_TCP_OFFLINE_HIT;
+ http->logType.update(LOG_TCP_OFFLINE_HIT);
sendMoreData(result);
}
/// Deny loops
if (r->flags.loopDetected) {
http->al->http.code = Http::scForbidden;
- err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL, http->getConn()->clientConnection->remote, http->request);
+ err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, nullptr, http->getConn(), http->request, http->al);
createStoreEntry(r->method, RequestFlags());
errorAppendEntry(http->storeEntry(), err);
triggerInitialStoreRead();
triggerInitialStoreRead();
if (http->redirect.status) {
- HttpReply *rep = new HttpReply;
- http->logType = LOG_TCP_REDIRECT;
+ const HttpReplyPointer rep(new HttpReply);
+ http->logType.update(LOG_TCP_REDIRECT);
http->storeEntry()->releaseRequest();
rep->redirect(http->redirect.status, http->redirect.location);
http->storeEntry()->replaceHttpReply(rep);
assert(r->clientConnectionManager == http->getConn());
+ Comm::ConnectionPointer conn = http->getConn() != nullptr ? http->getConn()->clientConnection : nullptr;
/** Start forwarding to get the new object from network */
- Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConnection : NULL;
FwdState::Start(conn, http->storeEntry(), r, http->al);
}
}
debugs(88, 4, http->request->method << ' ' << http->uri);
http->al->http.code = Http::scGatewayTimeout;
ErrorState *err = clientBuildError(ERR_ONLY_IF_CACHED_MISS, Http::scGatewayTimeout, NULL,
- http->getConn()->clientConnection->remote, http->request);
+ http->getConn(), http->request, http->al);
removeClientStoreReference(&sc, http);
startError(err);
}
{
StoreEntry *const e = http->storeEntry();
- if (e->getReply()->sline.status() != Http::scOkay) {
- debugs(88, 4, "Reply code " << e->getReply()->sline.status() << " != 200");
- http->logType = LOG_TCP_MISS;
+ const auto replyStatusCode = e->mem().baseReply().sline.status();
+ if (replyStatusCode != Http::scOkay) {
+ debugs(88, 4, "miss because " << replyStatusCode << " != 200");
+ http->logType.update(LOG_TCP_MISS);
processMiss();
return true;
}
if (http->flags.internal)
return false; // internal content "hits" cannot be blocked
- if (const HttpReply *rep = http->storeEntry()->getReply()) {
+ const auto &rep = http->storeEntry()->mem().freshestReply();
+ {
std::unique_ptr<ACLFilledChecklist> chl(clientAclChecklistCreate(Config.accessList.sendHit, http));
- chl->reply = const_cast<HttpReply*>(rep); // ACLChecklist API bug
+ chl->reply = const_cast<HttpReply*>(&rep); // ACLChecklist API bug
HTTPMSGLOCK(chl->reply);
- return chl->fastCheck() != ACCESS_ALLOWED; // when in doubt, block
+ return !chl->fastCheck().allowed(); // when in doubt, block
}
-
- // This does not happen, I hope, because we are called from CacheHit, which
- // is called via a storeClientCopy() callback, and store should initialize
- // the reply before calling that callback.
- debugs(88, 3, "Missing reply!");
- return false;
-}
-
-void
-clientReplyContext::purgeRequestFindObjectToPurge()
-{
- /* Try to find a base entry */
- http->flags.purging = true;
- lookingforstore = 1;
-
- // TODO: can we use purgeAllCached() here instead of doing the
- // getPublicByRequestMethod() dance?
- StoreEntry::getPublicByRequestMethod(this, http->request, Http::METHOD_GET);
}
// Purges all entries with a given url
void
purgeEntriesByUrl(HttpRequest * req, const char *url)
{
-#if USE_HTCP
- bool get_or_head_sent = false;
-#endif
-
for (HttpRequestMethod m(Http::METHOD_NONE); m != Http::METHOD_ENUM_END; ++m) {
if (m.respMaybeCacheable()) {
- if (StoreEntry *entry = storeGetPublic(url, m)) {
- debugs(88, 5, "purging " << *entry << ' ' << m << ' ' << url);
+ const cache_key *key = storeKeyPublic(url, m);
+ debugs(88, 5, m << ' ' << url << ' ' << storeKeyText(key));
#if USE_HTCP
- neighborsHtcpClear(entry, url, req, m, HTCP_CLR_INVALIDATION);
- if (m == Http::METHOD_GET || m == Http::METHOD_HEAD) {
- get_or_head_sent = true;
- }
+ neighborsHtcpClear(nullptr, req, m, HTCP_CLR_INVALIDATION);
#endif
- entry->release();
- }
+ Store::Root().evictIfFound(key);
}
}
-
-#if USE_HTCP
- if (!get_or_head_sent) {
- neighborsHtcpClear(NULL, url, req, HttpRequestMethod(Http::METHOD_GET), HTCP_CLR_INVALIDATION);
- }
-#endif
}
void
purgeEntriesByUrl(http->request, url.c_str());
}
-void
-clientReplyContext::created(StoreEntry *newEntry)
-{
- if (lookingforstore == 1)
- purgeFoundGet(newEntry);
- else if (lookingforstore == 2)
- purgeFoundHead(newEntry);
- else if (lookingforstore == 3)
- purgeDoPurgeGet(newEntry);
- else if (lookingforstore == 4)
- purgeDoPurgeHead(newEntry);
- else if (lookingforstore == 5)
- identifyFoundObject(newEntry);
-}
-
-void
-clientReplyContext::purgeFoundGet(StoreEntry *newEntry)
-{
- if (newEntry->isNull()) {
- lookingforstore = 2;
- StoreEntry::getPublicByRequestMethod(this, http->request, Http::METHOD_HEAD);
- } else
- purgeFoundObject (newEntry);
-}
-
-void
-clientReplyContext::purgeFoundHead(StoreEntry *newEntry)
-{
- if (newEntry->isNull())
- purgeDoMissPurge();
- else
- purgeFoundObject (newEntry);
-}
-
-void
-clientReplyContext::purgeFoundObject(StoreEntry *entry)
+LogTags *
+clientReplyContext::loggingTags() const
{
- assert (entry && !entry->isNull());
-
- if (EBIT_TEST(entry->flags, ENTRY_SPECIAL)) {
- http->logType = LOG_TCP_DENIED;
- ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL,
- http->getConn()->clientConnection->remote, http->request);
- startError(err);
- return; // XXX: leaking unused entry if some store does not keep it
- }
-
- StoreIOBuffer localTempBuffer;
- /* Swap in the metadata */
- http->storeEntry(entry);
-
- http->storeEntry()->lock("clientReplyContext::purgeFoundObject");
- http->storeEntry()->createMemObject(storeId(), http->log_uri,
- http->request->method);
-
- sc = storeClientListAdd(http->storeEntry(), this);
-
- http->logType = LOG_TCP_HIT;
-
- reqofs = 0;
-
- localTempBuffer.offset = http->out.offset;
-
- localTempBuffer.length = next()->readBuffer.length;
-
- localTempBuffer.data = next()->readBuffer.data;
-
- storeClientCopy(sc, http->storeEntry(),
- localTempBuffer, CacheHit, this);
+ // XXX: clientReplyContext code assumes that http cbdata is always valid.
+ // TODO: Either add cbdataReferenceValid(http) checks in all the relevant
+ // places, like this one, or remove cbdata protection of the http member.
+ return &http->logType;
}
void
Config2.onoff.enable_purge);
if (!Config2.onoff.enable_purge) {
- http->logType = LOG_TCP_DENIED;
- ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL, http->getConn()->clientConnection->remote, http->request);
+ http->logType.update(LOG_TCP_DENIED);
+ ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL,
+ http->getConn(), http->request, http->al);
startError(err);
return;
}
/* Release both IP cache */
ipcacheInvalidate(http->request->url.host());
- if (!http->flags.purging)
- purgeRequestFindObjectToPurge();
- else
- purgeDoMissPurge();
-}
-
-void
-clientReplyContext::purgeDoMissPurge()
-{
- http->logType = LOG_TCP_MISS;
- lookingforstore = 3;
- StoreEntry::getPublicByRequestMethod(this,http->request, Http::METHOD_GET);
+ // TODO: can we use purgeAllCached() here instead?
+ purgeDoPurge();
}
void
-clientReplyContext::purgeDoPurgeGet(StoreEntry *newEntry)
-{
- assert (newEntry);
- /* Move to new() when that is created */
- purgeStatus = Http::scNotFound;
-
- if (!newEntry->isNull()) {
- /* Release the cached URI */
- debugs(88, 4, "clientPurgeRequest: GET '" << newEntry->url() << "'" );
-#if USE_HTCP
- neighborsHtcpClear(newEntry, NULL, http->request, HttpRequestMethod(Http::METHOD_GET), HTCP_CLR_PURGE);
-#endif
- newEntry->release();
- purgeStatus = Http::scOkay;
+clientReplyContext::purgeDoPurge()
+{
+ auto firstFound = false;
+ if (const auto entry = storeGetPublicByRequestMethod(http->request, Http::METHOD_GET)) {
+ // special entries are only METHOD_GET entries without variance
+ if (EBIT_TEST(entry->flags, ENTRY_SPECIAL)) {
+ http->logType.update(LOG_TCP_DENIED);
+ const auto err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, nullptr,
+ http->getConn(), http->request, http->al);
+ startError(err);
+ entry->abandon(__FUNCTION__);
+ return;
+ }
+ firstFound = true;
+ if (!purgeEntry(*entry, Http::METHOD_GET))
+ return;
}
- lookingforstore = 4;
- StoreEntry::getPublicByRequestMethod(this, http->request, Http::METHOD_HEAD);
-}
+ detailStoreLookup(storeLookupString(firstFound));
-void
-clientReplyContext::purgeDoPurgeHead(StoreEntry *newEntry)
-{
- if (newEntry && !newEntry->isNull()) {
- debugs(88, 4, "HEAD " << newEntry->url());
-#if USE_HTCP
- neighborsHtcpClear(newEntry, NULL, http->request, HttpRequestMethod(Http::METHOD_HEAD), HTCP_CLR_PURGE);
-#endif
- newEntry->release();
- purgeStatus = Http::scOkay;
+ if (const auto entry = storeGetPublicByRequestMethod(http->request, Http::METHOD_HEAD)) {
+ if (!purgeEntry(*entry, Http::METHOD_HEAD))
+ return;
}
/* And for Vary, release the base URI if none of the headers was included in the request */
&& http->request->vary_headers.find('=') != SBuf::npos) {
// XXX: performance regression, c_str() reallocates
SBuf tmp(http->request->effectiveRequestUri());
- StoreEntry *entry = storeGetPublic(tmp.c_str(), Http::METHOD_GET);
- if (entry) {
- debugs(88, 4, "Vary GET " << entry->url());
-#if USE_HTCP
- neighborsHtcpClear(entry, NULL, http->request, HttpRequestMethod(Http::METHOD_GET), HTCP_CLR_PURGE);
-#endif
- entry->release();
- purgeStatus = Http::scOkay;
+ if (const auto entry = storeGetPublic(tmp.c_str(), Http::METHOD_GET)) {
+ if (!purgeEntry(*entry, Http::METHOD_GET, "Vary "))
+ return;
}
- entry = storeGetPublic(tmp.c_str(), Http::METHOD_HEAD);
-
- if (entry) {
- debugs(88, 4, "Vary HEAD " << entry->url());
-#if USE_HTCP
- neighborsHtcpClear(entry, NULL, http->request, HttpRequestMethod(Http::METHOD_HEAD), HTCP_CLR_PURGE);
-#endif
- entry->release();
- purgeStatus = Http::scOkay;
+ if (const auto entry = storeGetPublic(tmp.c_str(), Http::METHOD_HEAD)) {
+ if (!purgeEntry(*entry, Http::METHOD_HEAD, "Vary "))
+ return;
}
}
+ if (purgeStatus == Http::scNone)
+ purgeStatus = Http::scNotFound;
+
/*
* Make a new entry to hold the reply to be written
* to the client.
*/
- /* FIXME: This doesn't need to go through the store. Simply
+ /* TODO: This doesn't need to go through the store. Simply
* push down the client chain
*/
createStoreEntry(http->request->method, RequestFlags());
triggerInitialStoreRead();
- HttpReply *rep = new HttpReply;
+ const HttpReplyPointer rep(new HttpReply);
rep->setHeaders(purgeStatus, NULL, NULL, 0, 0, -1);
http->storeEntry()->replaceHttpReply(rep);
http->storeEntry()->complete();
}
+bool
+clientReplyContext::purgeEntry(StoreEntry &entry, const Http::MethodType methodType, const char *descriptionPrefix)
+{
+ debugs(88, 4, descriptionPrefix << Http::MethodStr(methodType) << " '" << entry.url() << "'" );
+#if USE_HTCP
+ neighborsHtcpClear(&entry, http->request, HttpRequestMethod(methodType), HTCP_CLR_PURGE);
+#endif
+ entry.release(true);
+ purgeStatus = Http::scOkay;
+ return true;
+}
+
void
clientReplyContext::traceReply(clientStreamNode * node)
{
localTempBuffer, SendMoreData, this);
http->storeEntry()->releaseRequest();
http->storeEntry()->buffer();
- HttpReply *rep = new HttpReply;
+ const HttpReplyPointer rep(new HttpReply);
rep->setHeaders(Http::scOkay, NULL, "text/plain", http->request->prefixLen(), 0, squid_curtime);
http->storeEntry()->replaceHttpReply(rep);
http->request->swapOut(http->storeEntry());
/* haven't found end of headers yet */
return 0;
- const HttpReplyPointer curReply(mem->getReply());
+ // TODO: Use MemObject::expectedReplySize(method) after resolving XXX below.
+ const auto expectedBodySize = mem->baseReply().content_length;
+
+ // XXX: The code below talks about sending data, and checks stats about
+ // bytes written to the client connection, but this method must determine
+ // whether we are done _receiving_ data from Store. This code should work OK
+ // when expectedBodySize is unknown or matches written data, but it may
+ // malfunction when we are writing ranges while receiving a full response.
/*
* Figure out how much data we are supposed to send.
* If we are sending a body and we don't have a content-length,
* then we must wait for the object to become STORE_OK.
*/
- if (curReply->content_length < 0)
+ if (expectedBodySize < 0)
return 0;
- uint64_t expectedLength = curReply->content_length + http->out.headers_sz;
+ const uint64_t expectedLength = expectedBodySize + http->out.headers_sz;
if (http->out.size < expectedLength)
return 0;
#if SIZEOF_INT64_T == 4
if (http->out.size > 0x7FFF0000) {
debugs(88, DBG_IMPORTANT, "WARNING: closing FD " << fd << " to prevent out.size counter overflow");
- debugs(88, DBG_IMPORTANT, "\tclient " << http->getConn()->peer);
+ if (http->getConn())
+ debugs(88, DBG_IMPORTANT, "\tclient " << http->getConn()->peer);
debugs(88, DBG_IMPORTANT, "\treceived " << http->out.size << " bytes");
debugs(88, DBG_IMPORTANT, "\tURI " << http->log_uri);
return 1;
if (http->out.offset > 0x7FFF0000) {
debugs(88, DBG_IMPORTANT, "WARNING: closing FD " << fd < " to prevent out.offset counter overflow");
- debugs(88, DBG_IMPORTANT, "\tclient " << http->getConn()->peer);
+ if (http->getConn())
+ debugs(88, DBG_IMPORTANT, "\tclient " << http->getConn()->peer);
debugs(88, DBG_IMPORTANT, "\treceived " << http->out.size << " bytes, offset " << http->out.offset);
debugs(88, DBG_IMPORTANT, "\tURI " << http->log_uri);
return 1;
return STREAM_FAILED;
}
+ // TODO: See also (and unify with) storeNotOKTransferDone() checks.
const int64_t expectedBodySize =
- http->storeEntry()->getReply()->bodySize(http->request->method);
+ http->storeEntry()->mem().baseReply().bodySize(http->request->method);
if (expectedBodySize >= 0 && !http->gotEnough()) {
debugs(88, 5, "clientReplyStatus: client didn't get all it expected");
return STREAM_UNPLANNED_COMPLETE;
/* Responses with no body will not have a content-type header,
* which breaks the rep_mime_type acl, which
* coincidentally, is the most common acl for reply access lists.
- * A better long term fix for this is to allow acl matchs on the various
+ * A better long term fix for this is to allow acl matches on the various
* status codes, and then supply a default ruleset that puts these
* codes before any user defines access entries. That way the user
* can choose to block these responses where appropriate, but won't get
}
reply->header.removeHopByHopEntries();
+ // paranoid: ContentLengthInterpreter has cleaned non-generated replies
+ reply->removeIrrelevantContentLength();
// if (request->range)
// clientBuildRangeHeader(http, reply);
/* Signal old objects. NB: rfc 2616 is not clear,
* by implication, on whether we should do this to all
* responses, or only cache hits.
- * 14.46 states it ONLY applys for heuristically caclulated
+ * 14.46 states it ONLY applies for heuristically calculated
* freshness values, 13.2.4 doesn't specify the same limitation.
* We interpret RFC 2616 under the combination.
*/
/* TODO: if maxage or s-maxage is present, don't do this */
- if (squid_curtime - http->storeEntry()->timestamp >= 86400) {
- char tbuf[512];
- snprintf (tbuf, sizeof(tbuf), "%s %s %s",
- "113", ThisCache,
- "This cache hit is still fresh and more than 1 day old");
- hdr->putStr(Http::HdrType::WARNING, tbuf);
- }
+ if (squid_curtime - http->storeEntry()->timestamp >= 86400)
+ hdr->putWarning(113, "This cache hit is still fresh and more than 1 day old");
}
}
reply->sline.status() == Http::scUnauthorized)
) {
/* Add authentication header */
- /*! \todo alter errorstate to be accel on|off aware. The 0 on the next line
+ /* TODO: alter errorstate to be accel on|off aware. The 0 on the next line
* depends on authenticate behaviour: all schemes to date send no extra
* data on 407/401 responses, and do not check the accel state on 401/407
* responses
*/
- authenticateFixHeader(reply, request->auth_user_request, request, 0, 1);
+ Auth::UserRequest::AddReplyAuthHeader(reply, request->auth_user_request, request, 0, 1);
} else if (request->auth_user_request != NULL)
- authenticateFixHeader(reply, request->auth_user_request, request, http->flags.accel, 0);
+ Auth::UserRequest::AddReplyAuthHeader(reply, request->auth_user_request, request, http->flags.accel, 0);
#endif
- /* Append X-Cache */
- httpHeaderPutStrf(hdr, Http::HdrType::X_CACHE, "%s from %s",
- is_hit ? "HIT" : "MISS", getMyHostname());
-
-#if USE_CACHE_DIGESTS
- /* Append X-Cache-Lookup: -- temporary hack, to be removed @?@ @?@ */
- httpHeaderPutStrf(hdr, Http::HdrType::X_CACHE_LOOKUP, "%s from %s:%d",
- lookup_type ? lookup_type : "NONE",
- getMyHostname(), getMyPort());
-
-#endif
+ SBuf cacheStatus(uniqueHostname());
+ if (const auto hitOrFwd = http->logType.cacheStatusSource())
+ cacheStatus.append(hitOrFwd);
+ if (firstStoreLookup_) {
+ cacheStatus.append(";detail=");
+ cacheStatus.append(firstStoreLookup_);
+ }
+ // TODO: Remove c_str() after converting HttpHeaderEntry::value to SBuf
+ hdr->putStr(Http::HdrType::CACHE_STATUS, cacheStatus.c_str());
const bool maySendChunkedReply = !request->multipartRangeRequest() &&
- reply->sline.protocol == AnyP::PROTO_HTTP && // response is HTTP
+ reply->sline.version.protocol == AnyP::PROTO_HTTP && // response is HTTP
(request->http_ver >= Http::ProtocolVersion(1,1));
/* Check whether we should send keep-alive */
{
assert(reply == NULL);
- reply = http->storeEntry()->getReply()->clone();
+ reply = http->storeEntry()->mem().freshestReply().clone();
HTTPMSGLOCK(reply);
- if (reply->sline.protocol == AnyP::PROTO_HTTP) {
+ http->al->reply = reply;
+
+ if (reply->sline.version.protocol == AnyP::PROTO_HTTP) {
/* RFC 2616 requires us to advertise our version (but only on real HTTP traffic) */
reply->sline.version = Http::ProtocolVersion();
}
// client sent CC:no-cache or some other condition has been
// encountered which prevents delivering a public/cached object.
if (!r->flags.noCache || r->flags.internal) {
- lookingforstore = 5;
- StoreEntry::getPublicByRequest (this, r);
+ const auto e = storeGetPublicByRequest(r);
+ identifyFoundObject(e, storeLookupString(bool(e)));
} else {
- identifyFoundObject (NullStoreEntry::getInstance());
+ // "external" no-cache requests skip Store lookups
+ identifyFoundObject(nullptr, "no-cache");
}
}
* to see if we can determine the final status of the request.
*/
void
-clientReplyContext::identifyFoundObject(StoreEntry *newEntry)
+clientReplyContext::identifyFoundObject(StoreEntry *newEntry, const char *detail)
{
- StoreEntry *e = newEntry;
- HttpRequest *r = http->request;
-
- /** \li If the entry received isNull() then we ignore it. */
- if (e->isNull()) {
- http->storeEntry(NULL);
- } else {
- http->storeEntry(e);
- }
+ detailStoreLookup(detail);
- e = http->storeEntry();
+ HttpRequest *r = http->request;
+ http->storeEntry(newEntry);
+ const auto e = http->storeEntry();
/* Release IP-cache entries on reload */
/** \li If the request has no-cache flag set or some no_cache HACK in operation we
if (r->flags.noCache || r->flags.noCacheHack())
ipcacheInvalidateNegative(r->url.host());
-#if USE_CACHE_DIGESTS
- lookup_type = http->storeEntry() ? "HIT" : "MISS";
-#endif
-
- if (NULL == http->storeEntry()) {
+ if (!e) {
/** \li If no StoreEntry object is current assume this object isn't in the cache set MISS*/
debugs(85, 3, "StoreEntry is NULL - MISS");
- http->logType = LOG_TCP_MISS;
+ http->logType.update(LOG_TCP_MISS);
doGetMoreData();
return;
}
if (Config.onoff.offline) {
/** \li If we are running in offline mode set to HIT */
debugs(85, 3, "offline HIT " << *e);
- http->logType = LOG_TCP_HIT;
+ http->logType.update(LOG_TCP_HIT);
doGetMoreData();
return;
}
/** \li If redirection status is True force this to be a MISS */
debugs(85, 3, "REDIRECT status forced StoreEntry to NULL (no body on 3XX responses) " << *e);
forgetHit();
- http->logType = LOG_TCP_REDIRECT;
+ http->logType.update(LOG_TCP_REDIRECT);
doGetMoreData();
return;
}
if (!e->validToSend()) {
debugs(85, 3, "!storeEntryValidToSend MISS " << *e);
forgetHit();
- http->logType = LOG_TCP_MISS;
+ http->logType.update(LOG_TCP_MISS);
doGetMoreData();
return;
}
if (EBIT_TEST(e->flags, ENTRY_SPECIAL)) {
/* \li Special entries are always hits, no matter what the client says */
debugs(85, 3, "ENTRY_SPECIAL HIT " << *e);
- http->logType = LOG_TCP_HIT;
+ http->logType.update(LOG_TCP_HIT);
doGetMoreData();
return;
}
if (r->flags.noCache) {
debugs(85, 3, "no-cache REFRESH MISS " << *e);
forgetHit();
- http->logType = LOG_TCP_CLIENT_REFRESH_MISS;
+ http->logType.update(LOG_TCP_CLIENT_REFRESH_MISS);
+ doGetMoreData();
+ return;
+ }
+
+ if (e->hittingRequiresCollapsing() && !startCollapsingOn(*e, false)) {
+ debugs(85, 3, "prohibited CF MISS " << *e);
+ forgetHit();
+ http->logType.update(LOG_TCP_MISS);
doGetMoreData();
return;
}
debugs(85, 3, "default HIT " << *e);
- http->logType = LOG_TCP_HIT;
+ http->logType.update(LOG_TCP_HIT);
doGetMoreData();
}
+/// remembers the very first Store lookup classification, ignoring the rest
+void
+clientReplyContext::detailStoreLookup(const char *detail)
+{
+ if (!firstStoreLookup_) {
+ debugs(85, 7, detail);
+ firstStoreLookup_ = detail;
+ } else {
+ debugs(85, 7, "ignores " << detail << " after " << firstStoreLookup_);
+ }
+}
+
/**
* Request more data from the store for the client Stream
* This is *the* entry point to this module.
}
/* continue forwarding, not finished yet. */
- http->logType = LOG_TCP_MISS;
+ http->logType.update(LOG_TCP_MISS);
context->doGetMoreData();
} else
http->storeEntry()->lock("clientReplyContext::doGetMoreData");
- MemObject *mem_obj = http->storeEntry()->makeMemObject();
- if (!mem_obj->hasUris()) {
- /*
- * This if-block exists because we don't want to clobber
- * a preexiting mem_obj->method value if the mem_obj
- * already exists. For example, when a HEAD request
- * is a cache hit for a GET response, we want to keep
- * the method as GET.
- */
- mem_obj->setUris(storeId(), http->log_uri, http->request->method);
- /**
- * Here we can see if the object was
- * created using URL or alternative StoreID from helper.
- */
- debugs(88, 3, "storeId: " << http->storeEntry()->mem_obj->storeId());
- }
+ http->storeEntry()->ensureMemObject(storeId(), http->log_uri, http->request->method);
sc = storeClientListAdd(http->storeEntry(), this);
#if USE_DELAY_POOLS
assert(http->out.size == 0);
assert(http->out.offset == 0);
- if (Ip::Qos::TheConfig.isHitTosActive()) {
- Ip::Qos::doTosLocalHit(http->getConn()->clientConnection);
- }
+ if (ConnStateData *conn = http->getConn()) {
+ if (Ip::Qos::TheConfig.isHitTosActive()) {
+ Ip::Qos::doTosLocalHit(conn->clientConnection);
+ }
- if (Ip::Qos::TheConfig.isHitNfmarkActive()) {
- Ip::Qos::doNfmarkLocalHit(http->getConn()->clientConnection);
+ if (Ip::Qos::TheConfig.isHitNfmarkActive()) {
+ Ip::Qos::doNfmarkLocalHit(conn->clientConnection);
+ }
}
localTempBuffer.offset = reqofs;
void
clientReplyContext::makeThisHead()
{
- /* At least, I think thats what this does */
+ /* At least, I think that's what this does */
dlinkDelete(&http->active, &ClientActiveRequests);
dlinkAdd(http, &http->active, &ClientActiveRequests);
}
* We call into the stream, because we don't know that there is a
* client socket!
*/
- debugs(88, 5, "clientReplyContext::sendStreamError: A stream error has occured, marking as complete and sending no data.");
+ debugs(88, 5, "A stream error has occurred, marking as complete and sending no data.");
StoreIOBuffer localTempBuffer;
flags.complete = 1;
http->request->flags.streamError = true;
void
clientReplyContext::sendBodyTooLargeError()
{
- Ip::Address tmp_noaddr;
- tmp_noaddr.setNoAddr(); // TODO: make a global const
- http->logType = LOG_TCP_DENIED_REPLY;
+ http->logType.update(LOG_TCP_DENIED_REPLY);
ErrorState *err = clientBuildError(ERR_TOO_BIG, Http::scForbidden, NULL,
- http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmp_noaddr,
- http->request);
+ http->getConn(), http->request, http->al);
removeClientStoreReference(&(sc), http);
HTTPMSGUNLOCK(reply);
startError(err);
void
clientReplyContext::sendPreconditionFailedError()
{
- http->logType = LOG_TCP_HIT;
+ http->logType.update(LOG_TCP_HIT);
ErrorState *const err =
clientBuildError(ERR_PRECONDITION_FAILED, Http::scPreconditionFailed,
- NULL, http->getConn()->clientConnection->remote, http->request);
+ nullptr, http->getConn(), http->request, http->al);
removeClientStoreReference(&sc, http);
HTTPMSGUNLOCK(reply);
startError(err);
{
StoreEntry *e = http->storeEntry();
const time_t timestamp = e->timestamp;
- HttpReply *const temprep = e->getReply()->make304();
+ const auto temprep = e->mem().freshestReply().make304();
// log as TCP_INM_HIT if code 304 generated for
// If-None-Match request
if (!http->request->flags.ims)
- http->logType = LOG_TCP_INM_HIT;
+ http->logType.update(LOG_TCP_INM_HIT);
else
- http->logType = LOG_TCP_IMS_HIT;
+ http->logType.update(LOG_TCP_IMS_HIT);
removeClientStoreReference(&sc, http);
createStoreEntry(http->request->method, RequestFlags());
e = http->storeEntry();
}
void
-clientReplyContext::ProcessReplyAccessResult(allow_t rv, void *voidMe)
+clientReplyContext::ProcessReplyAccessResult(Acl::Answer rv, void *voidMe)
{
clientReplyContext *me = static_cast<clientReplyContext *>(voidMe);
me->processReplyAccessResult(rv);
}
void
-clientReplyContext::processReplyAccessResult(const allow_t &accessAllowed)
+clientReplyContext::processReplyAccessResult(const Acl::Answer &accessAllowed)
{
debugs(88, 2, "The reply for " << http->request->method
<< ' ' << http->uri << " is " << accessAllowed << ", because it matched "
<< (AclMatchedName ? AclMatchedName : "NO ACL's"));
- if (accessAllowed != ACCESS_ALLOWED) {
+ if (!accessAllowed.allowed()) {
ErrorState *err;
err_type page_id;
page_id = aclGetDenyInfoPage(&Config.denyInfoList, AclMatchedName, 1);
- http->logType = LOG_TCP_DENIED_REPLY;
+ http->logType.update(LOG_TCP_DENIED_REPLY);
if (page_id == ERR_NONE)
page_id = ERR_ACCESS_DENIED;
- Ip::Address tmp_noaddr;
- tmp_noaddr.setNoAddr();
err = clientBuildError(page_id, Http::scForbidden, NULL,
- http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmp_noaddr,
- http->request);
+ http->getConn(), http->request, http->al);
removeClientStoreReference(&sc, http);
sc->setDelayId(DelayId::DelayClient(http,reply));
#endif
- /* handle headers */
-
- if (Config.onoff.log_mime_hdrs) {
- size_t k;
-
- if ((k = headersEnd(buf, reqofs))) {
- safe_free(http->al->headers.reply);
- http->al->headers.reply = (char *)xcalloc(k + 1, 1);
- xstrncpy(http->al->headers.reply, buf, k);
- }
- }
-
holdingBuffer = result;
processReplyAccess();
return;
}
+void
+clientReplyContext::fillChecklist(ACLFilledChecklist &checklist) const
+{
+ clientAclChecklistFill(checklist, http);
+}
+
/* Using this breaks the client layering just a little!
*/
void
*/
if (http->request == NULL) {
- http->request = new HttpRequest(m, AnyP::PROTO_NONE, "http", null_string);
+ const MasterXaction::Pointer mx = new MasterXaction(XactionInitiator::initClient);
+ // XXX: These fake URI parameters shadow the real (or error:...) URI.
+ // TODO: Either always set the request earlier and assert here OR use
+ // http->uri (converted to Anyp::Uri) to create this catch-all request.
+ const_cast<HttpRequest *&>(http->request) = new HttpRequest(m, AnyP::PROTO_NONE, "http", null_string, mx);
HTTPMSGLOCK(http->request);
}
StoreEntry *e = storeCreateEntry(storeId(), http->log_uri, reqFlags, m);
- // Make entry collapsable ASAP, to increase collapsing chances for others,
+ // Make entry collapsible ASAP, to increase collapsing chances for others,
// TODO: every must-revalidate and similar request MUST reach the origin,
// but do we have to prohibit others from collapsing on that request?
- if (Config.onoff.collapsed_forwarding && reqFlags.cachable &&
+ if (reqFlags.cachable &&
!reqFlags.needValidation &&
- (m == Http::METHOD_GET || m == Http::METHOD_HEAD)) {
+ (m == Http::METHOD_GET || m == Http::METHOD_HEAD) &&
+ mayInitiateCollapsing()) {
// make the entry available for future requests now
- Store::Root().allowCollapsing(e, reqFlags, m);
+ (void)Store::Root().allowCollapsing(e, reqFlags, m);
}
sc = storeClientListAdd(e, this);
/* So, we mark the store logic as complete */
flags.storelogiccomplete = 1;
- /* and get the caller to request a read, from whereever they are */
+ /* and get the caller to request a read, from wherever they are */
/* NOTE: after ANY data flows down the pipe, even one step,
* this function CAN NOT be used to manage errors
*/
ErrorState *
clientBuildError(err_type page_id, Http::StatusCode status, char const *url,
- Ip::Address &src_addr, HttpRequest * request)
+ const ConnStateData *conn, HttpRequest *request, const AccessLogEntry::Pointer &al)
{
- ErrorState *err = new ErrorState(page_id, status, request);
- err->src_addr = src_addr;
+ const auto err = new ErrorState(page_id, status, request, al);
+ err->src_addr = conn && conn->clientConnection ? conn->clientConnection->remote : Ip::Address::NoAddr();
if (url)
err->url = xstrdup(url);