void
FwdState::complete()
{
- debugs(17, 3, HERE << entry->url() << "\n\tstatus " << entry->getReply()->sline.status());
+ const auto replyStatus = entry->mem().baseReply().sline.status();
+ debugs(17, 3, *entry << " status " << replyStatus << ' ' << entry->url());
#if URL_CHECKSUM_DEBUG
entry->mem_obj->checkUrlChecksum();
#endif
- logReplyStatus(n_tries, entry->getReply()->sline.status());
+ logReplyStatus(n_tries, replyStatus);
if (reforward()) {
- debugs(17, 3, HERE << "re-forwarding " << entry->getReply()->sline.status() << " " << entry->url());
+ debugs(17, 3, "re-forwarding " << replyStatus << " " << entry->url());
if (Comm::IsConnOpen(serverConn))
unregister(serverConn);
} else {
if (Comm::IsConnOpen(serverConn))
- debugs(17, 3, HERE << "server FD " << serverConnection()->fd << " not re-forwarding status " << entry->getReply()->sline.status());
+ debugs(17, 3, "server FD " << serverConnection()->fd << " not re-forwarding status " << replyStatus);
else
- debugs(17, 3, HERE << "server (FD closed) not re-forwarding status " << entry->getReply()->sline.status());
+ debugs(17, 3, "server (FD closed) not re-forwarding status " << replyStatus);
entry->complete();
if (!Comm::IsConnOpen(serverConn))
return 0;
}
- const Http::StatusCode s = e->getReply()->sline.status();
+ const auto s = entry->mem().baseReply().sline.status();
debugs(17, 3, HERE << "status " << s);
return reforwardableStatus(s);
}
httpHeaderMaskInit(&mask, 0);
}
+// XXX: Delete as unused, expensive, and violating copy semantics by skipping Warnings
HttpHeader::HttpHeader(const HttpHeader &other): owner(other.owner), len(other.len), conflictingContentLength_(false)
{
entries.reserve(other.entries.capacity());
clean();
}
+// XXX: Delete as unused, expensive, and violating assignment semantics by skipping Warnings
HttpHeader &
HttpHeader::operator =(const HttpHeader &other)
{
}
}
-/// check whether the fresh header has any new/changed updatable fields
bool
HttpHeader::needUpdate(HttpHeader const *fresh) const
{
+ // our 1xx Warnings must be removed
+ for (const auto e: entries) {
+ // TODO: Move into HttpHeaderEntry::is1xxWarning() before official commit.
+ if (e && e->id == Http::HdrType::WARNING && (e->getInt()/100 == 1))
+ return true;
+ }
+
for (const auto e: fresh->entries) {
if (!e || skipUpdateHeader(e->id))
continue;
bool
HttpHeader::skipUpdateHeader(const Http::HdrType id) const
{
- // RFC 7234, section 4.3.4: use fields other from Warning for update
- return id == Http::HdrType::WARNING;
+ return
+ // RFC 7234, section 4.3.4: use header fields other than Warning
+ (id == Http::HdrType::WARNING) ||
+ // TODO: Consider updating Vary headers after comparing the magnitude of
+ // the required changes (and/or cache losses) with compliance gains.
+ (id == Http::HdrType::VARY);
}
-bool
+void
HttpHeader::update(HttpHeader const *fresh)
{
assert(fresh);
assert(this != fresh);
- // Optimization: Finding whether a header field changed is expensive
- // and probably not worth it except for collapsed revalidation needs.
- if (Config.onoff.collapsed_forwarding && !needUpdate(fresh))
- return false;
-
updateWarnings();
const HttpHeaderEntry *e;
addEntry(e->clone());
}
- return true;
}
bool
/* Interface functions */
void clean();
void append(const HttpHeader * src);
- bool update(HttpHeader const *fresh);
+ /// replaces fields with matching names and adds fresh fields with new names
+ /// also updates Http::HdrType::WARNINGs, assuming `fresh` is a 304 reply
+ /// TODO: Refactor most callers to avoid special handling of WARNINGs.
+ void update(const HttpHeader *fresh);
+ /// \returns whether calling update(fresh) would change our set of fields
+ bool needUpdate(const HttpHeader *fresh) const;
void compact();
int parse(const char *header_start, size_t len, Http::ContentLengthInterpreter &interpreter);
/// Parses headers stored in a buffer.
/// *blk_end points to the first header delimiter character (CR or LF in CR?LF).
/// If block starts where it ends, then there are no fields in the header.
static bool Isolate(const char **parse_start, size_t l, const char **blk_start, const char **blk_end);
- bool needUpdate(const HttpHeader *fresh) const;
bool skipUpdateHeader(const Http::HdrType id) const;
void updateWarnings();
return rep;
}
-HttpReply *
+HttpReplyPointer
HttpReply::make304() const
{
static const Http::HdrType ImsEntries[] = {Http::HdrType::DATE, Http::HdrType::CONTENT_TYPE, Http::HdrType::EXPIRES, Http::HdrType::LAST_MODIFIED, /* eof */ Http::HdrType::OTHER};
- HttpReply *rv = new HttpReply;
+ const HttpReplyPointer rv(new HttpReply);
int t;
HttpHeaderEntry *e;
/* Not as efficient as skipping the header duplication,
* but easier to maintain
*/
- HttpReply *temp = make304();
+ const auto temp = make304();
MemBuf *rv = temp->pack();
- delete temp;
return rv;
}
return 1;
}
-bool
-HttpReply::updateOnNotModified(HttpReply const * freshRep)
+HttpReply::Pointer
+HttpReply::recreateOnNotModified(const HttpReply &reply304) const
{
- assert(freshRep);
-
- /* update raw headers */
- if (!header.update(&freshRep->header))
- return false;
+ // If enough 304s do not update, then this expensive checking is cheaper
+ // than blindly storing reply prefix identical to the already stored one.
+ if (!header.needUpdate(&reply304.header))
+ return nullptr;
- /* clean cache */
- hdrCacheClean();
-
- header.compact();
- /* init cache */
- hdrCacheInit();
-
- return true;
+ const Pointer cloned = clone();
+ cloned->header.update(&reply304.header);
+ cloned->hdrCacheClean();
+ cloned->header.compact();
+ cloned->hdrCacheInit();
+ return cloned;
}
/* internal routines */
virtual bool inheritProperties(const Http::Message *);
- bool updateOnNotModified(HttpReply const *other);
+ /// \returns nil (if no updates are necessary)
+ /// \returns a new reply combining this reply with 304 updates (otherwise)
+ Pointer recreateOnNotModified(const HttpReply &reply304) const;
/** set commonly used info with one call */
void setHeaders(Http::StatusCode status,
static HttpReplyPointer MakeConnectionEstablished();
/** construct a 304 reply and return it */
- HttpReply *make304() const;
+ HttpReplyPointer make304() const;
void redirect(Http::StatusCode, const char *);
ctx_exit(ctx); /* must exit before we free mem->url */
}
+HttpReply &
+MemObject::adjustableBaseReply()
+{
+ assert(!updatedReply_);
+ return *reply_;
+}
+
+void
+MemObject::replaceBaseReply(const HttpReplyPointer &r)
+{
+ assert(r);
+ reply_ = r;
+ updatedReply_ = nullptr;
+}
+
void
MemObject::write(const StoreIOBuffer &writeBuffer)
{
debugs(20, DBG_IMPORTANT, "MemObject->inmem_lo: " << inmem_lo);
debugs(20, DBG_IMPORTANT, "MemObject->nclients: " << nclients);
debugs(20, DBG_IMPORTANT, "MemObject->reply: " << reply_);
+ debugs(20, DBG_IMPORTANT, "MemObject->updatedReply: " << updatedReply_);
+ debugs(20, DBG_IMPORTANT, "MemObject->appliedUpdates: " << appliedUpdates);
debugs(20, DBG_IMPORTANT, "MemObject->request: " << request);
debugs(20, DBG_IMPORTANT, "MemObject->logUri: " << logUri_);
debugs(20, DBG_IMPORTANT, "MemObject->storeId: " << storeId_);
int64_t
MemObject::expectedReplySize() const
{
- debugs(20, 7, "object_sz: " << object_sz);
- if (object_sz >= 0) // complete() has been called; we know the exact answer
+ if (object_sz >= 0) {
+ debugs(20, 7, object_sz << " frozen by complete()");
return object_sz;
+ }
+
+ const auto hdr_sz = baseReply().hdr_sz;
- if (reply_) {
- const int64_t clen = reply_->bodySize(method);
- debugs(20, 7, "clen: " << clen);
- if (clen >= 0 && reply_->hdr_sz > 0) // yuck: Http::Message sets hdr_sz to 0
- return clen + reply_->hdr_sz;
+ // Cannot predict future length using an empty/unset or HTTP/0 reply.
+ // For any HTTP/1 reply, hdr_sz is positive -- status-line cannot be empty.
+ if (hdr_sz <= 0)
+ return -1;
+
+ const auto clen = baseReply().bodySize(method);
+ if (clen < 0) {
+ debugs(20, 7, "unknown; hdr: " << hdr_sz);
+ return -1;
}
- return -1; // not enough information to predict
+ const auto messageSize = clen + hdr_sz;
+ debugs(20, 7, messageSize << " hdr: " << hdr_sz << " clen: " << clen);
+ return messageSize;
}
void
data_hdr.freeContent();
inmem_lo = 0;
/* Should we check for clients? */
- if (reply_)
- reply_->reset();
+ assert(reply_);
+ reply_->reset();
+ updatedReply_ = nullptr;
+ appliedUpdates = false;
}
int64_t
bool
MemObject::readAheadPolicyCanRead() const
{
- const bool canRead = endOffset() - getReply()->hdr_sz <
+ const auto savedHttpHeaders = baseReply().hdr_sz;
+ const bool canRead = endOffset() - savedHttpHeaders <
lowestMemReaderOffset() + Config.readAheadGap;
if (!canRead) {
- debugs(19, 9, "no: " << endOffset() << '-' << getReply()->hdr_sz <<
+ debugs(19, 5, "no: " << endOffset() << '-' << savedHttpHeaders <<
" < " << lowestMemReaderOffset() << '+' << Config.readAheadGap);
}
void write(const StoreIOBuffer &buf);
void unlinkRequest() { request = nullptr; }
- const HttpReplyPointer &getReply() const { return reply_; }
- void replaceReply(const HttpReplyPointer &r) { reply_ = r; }
+
+ /// HTTP response before 304 (Not Modified) updates
+ /// starts "empty"; modified via replaceBaseReply() or adjustableBaseReply()
+ const HttpReply &baseReply() const { return *reply_; }
+
+ /// \returns nil -- if no 304 updates since replaceBaseReply()
+ /// \returns combination of baseReply() and 304 updates -- after updates
+ const HttpReplyPointer &updatedReply() const { return updatedReply_; }
+
+ /// \returns the updated-by-304(s) response (if it exists)
+ /// \returns baseReply() (otherwise)
+ const HttpReply &freshestReply() const {
+ if (updatedReply_)
+ return *updatedReply_;
+ else
+ return baseReply();
+ }
+
+ /// \returns writable base reply for parsing and other initial modifications
+ /// Base modifications can only be done when forming/loading the entry.
+ /// After that, use replaceBaseReply() to reset all of the replies.
+ HttpReply &adjustableBaseReply();
+
+ /// (re)sets base reply, usually just replacing the initial/empty object
+ /// also forgets the updated reply (if any)
+ void replaceBaseReply(const HttpReplyPointer &r);
+
+ /// (re)sets updated reply; \see updatedReply()
+ void updateReply(const HttpReply &r) { updatedReply_ = &r; }
+
+ /// reflects past Controller::updateOnNotModified(old, e304) calls:
+ /// for HTTP 304 entries: whether our entry was used as "e304"
+ /// for other entries: whether our entry was updated as "old"
+ bool appliedUpdates = false;
+
void stat (MemBuf * mb) const;
int64_t endOffset () const;
- void markEndOfReplyHeaders(); ///< sets reply_->hdr_sz to endOffset()
+
+ /// sets baseReply().hdr_sz (i.e. written reply headers size) to endOffset()
+ void markEndOfReplyHeaders();
+
/// negative if unknown; otherwise, expected object_sz, expected endOffset
/// maximum, and stored reply headers+body size (all three are the same)
int64_t expectedReplySize() const;
};
MemCache memCache; ///< current [shared] memory caching state for the entry
- /* Read only - this reply must be preserved by store clients */
- /* The original reply. possibly with updated metadata. */
HttpRequestPointer request;
struct timeval start_ping;
void kickReads();
private:
- HttpReplyPointer reply_;
+ HttpReplyPointer reply_; ///< \see baseReply()
+ HttpReplyPointer updatedReply_; ///< \see updatedReply()
mutable String storeId_; ///< StoreId for our entry (usually request URI)
mutable String logUri_; ///< URI used for logging (usually request URI)
// our +/- hdr_sz math below does not work if the chains differ [in size]
Must(update.stale.anchor->basics.swap_file_sz == update.fresh.anchor->basics.swap_file_sz);
- const HttpReply *rawReply = update.entry->getReply();
- Must(rawReply);
- const HttpReply &reply = *rawReply;
- const uint64_t staleHdrSz = reply.hdr_sz;
+ const uint64_t staleHdrSz = update.entry->mem().baseReply().hdr_sz;
debugs(20, 7, "stale hdr_sz: " << staleHdrSz);
/* we will need to copy same-slice payload after the stored headers later */
Must(update.stale.anchor);
ShmWriter writer(*this, update.entry, update.fresh.fileNo);
- reply.packHeadersUsingSlowPacker(writer);
+ update.entry->mem().freshestReply().packHeadersUsingSlowPacker(writer);
const uint64_t freshHdrSz = writer.totalWritten;
debugs(20, 7, "fresh hdr_sz: " << freshHdrSz << " diff: " << (freshHdrSz - staleHdrSz));
// from store_client::readBody()
// parse headers if needed; they might span multiple slices!
- HttpReply *rep = (HttpReply *)e.getReply();
+ const auto rep = &e.mem().adjustableBaseReply();
if (rep->pstate < Http::Message::psParsed) {
// XXX: have to copy because httpMsgParseStep() requires 0-termination
MemBuf mb;
StoreEntry();
virtual ~StoreEntry();
- HttpReply const *getReply() const;
+ MemObject &mem() { assert(mem_obj); return *mem_obj; }
+ const MemObject &mem() const { assert(mem_obj); return *mem_obj; }
+
+ /// \retval * the address of freshest reply (if mem_obj exists)
+ /// \retval nullptr when mem_obj does not exist
+ /// \see MemObject::freshestReply()
+ const HttpReply *hasFreshestReply() const { return mem_obj ? &mem_obj->freshestReply() : nullptr; }
+
void write(StoreIOBuffer);
/** Check if the Store entry is empty
* \retval false Store contains 1 or more bytes of data.
* \retval false Store contains negative content !!!!!!
*/
- bool isEmpty() const {
- assert (mem_obj);
- return mem_obj->endOffset() == 0;
- }
+ bool isEmpty() const { return mem().endOffset() == 0; }
bool isAccepting() const;
size_t bytesWanted(Range<size_t> const aRange, bool ignoreDelayPool = false) const;
/// flags [truncated or too big] entry with ENTRY_BAD_LENGTH and releases it
void lengthWentBad(const char *reason);
void complete();
store_client_t storeClientType() const;
- char const *getSerialisedMetaData();
+ /// \returns a malloc()ed buffer containing a length-long packed swap header
+ const char *getSerialisedMetaData(size_t &length) const;
/// Store a prepared error response. MemObject locks the reply object.
void storeErrorResponse(HttpReply *reply);
- void replaceHttpReply(HttpReply *, bool andStartWriting = true);
+ void replaceHttpReply(const HttpReplyPointer &, const bool andStartWriting = true);
void startWriting(); ///< pack and write reply headers and, maybe, body
/// whether we may start writing to disk (now or in the future)
bool mayStartSwapOut();
/// whether this entry has an ETag; if yes, puts ETag value into parameter
bool hasEtag(ETag &etag) const;
+ /// Updates easily-accessible non-Store-specific parts of the entry.
+ /// Use Controller::updateOnNotModified() instead of this helper.
+ /// \returns whether anything was actually updated
+ bool updateOnNotModified(const StoreEntry &e304);
+
/// the disk this entry is [being] cached on; asserts for entries w/o a disk
Store::Disk &disk() const;
/// whether one of this StoreEntry owners has locked the corresponding
ESIElement::Pointer cachedESITree;
#endif
- int64_t objectLen() const;
- int64_t contentLen() const;
+ int64_t objectLen() const { return mem().object_sz; }
+ int64_t contentLen() const { return objectLen() - mem().baseReply().hdr_sz; }
/// claim shared ownership of this entry (for use in a given context)
/// matching lock() and unlock() contexts eases leak triage but is optional
/// \ingroup SwapStoreAPI
char *storeSwapMetaPack(tlv * tlv_list, int *length);
/// \ingroup SwapStoreAPI
-tlv *storeSwapMetaBuild(StoreEntry * e);
+tlv *storeSwapMetaBuild(const StoreEntry *);
/// \ingroup SwapStoreAPI
void storeSwapTLVFree(tlv * n);
StoreMeta **tail;
};
-/*
- * store_swapmeta.c
- */
-char *storeSwapMetaPack(StoreMeta * tlv_list, int *length);
-StoreMeta *storeSwapMetaBuild(StoreEntry * e);
-StoreMeta *storeSwapMetaUnpack(const char *buf, int *hdrlen);
-void storeSwapTLVFree(StoreMeta * n);
-StoreMeta ** storeSwapTLVAdd(int type, const void *ptr, size_t len, StoreMeta ** tail);
-
#endif /* SQUID_TYPELENGTHVALUEUNPACKER_H */
debugs(53, DBG_IMPORTANT, "asHandleReply: Called with Error set and size=" << (unsigned int) result.length);
delete asState;
return;
- } else if (e->getReply()->sline.status() != Http::scOkay) {
+ } else if (e->mem().baseReply().sline.status() != Http::scOkay) {
debugs(53, DBG_IMPORTANT, "WARNING: AS " << asState->as_number << " whois request failed");
delete asState;
return;
al->url = log_uri;
debugs(33, 9, "clientLogRequest: al.url='" << al->url << "'");
- if (al->reply) {
- al->http.code = al->reply->sline.status();
- al->http.content_type = al->reply->content_type.termedBuf();
- } else if (loggingEntry() && loggingEntry()->mem_obj) {
- al->http.code = loggingEntry()->mem_obj->getReply()->sline.status();
- al->http.content_type = loggingEntry()->mem_obj->getReply()->content_type.termedBuf();
+ const auto findReply = [this]() -> const HttpReply * {
+ if (al->reply)
+ return al->reply.getRaw();
+ if (const auto le = loggingEntry())
+ return le->hasFreshestReply();
+ return nullptr;
+ };
+ if (const auto reply = findReply()) {
+ al->http.code = reply->sline.status();
+ al->http.content_type = reply->content_type.termedBuf();
}
debugs(33, 9, "clientLogRequest: http.code='" << al->http.code << "'");
while (pos != request->range->end()) {
/* account for headers for this range */
mb.reset();
- clientPackRangeHdr(memObject()->getReply(),
+ clientPackRangeHdr(&storeEntry()->mem().freshestReply(),
*pos, range_iter.boundary, &mb);
clen += mb.size;
varyEvaluateMatch(StoreEntry * entry, HttpRequest * request)
{
SBuf vary(request->vary_headers);
- int has_vary = entry->getReply()->header.has(Http::HdrType::VARY);
+ const auto &reply = entry->mem().freshestReply();
+ auto has_vary = reply.header.has(Http::HdrType::VARY);
#if X_ACCELERATOR_VARY
has_vary |=
- entry->getReply()->header.has(Http::HdrType::HDR_X_ACCELERATOR_VARY);
+ reply.header.has(Http::HdrType::HDR_X_ACCELERATOR_VARY);
#endif
if (!has_vary || entry->mem_obj->vary_headers.isEmpty()) {
/* virtual "vary" object found. Calculate the vary key and
* continue the search
*/
- vary = httpMakeVaryMark(request, entry->getReply());
+ vary = httpMakeVaryMark(request, &reply);
if (!vary.isEmpty()) {
request->vary_headers = vary;
}
} else {
if (vary.isEmpty()) {
- vary = httpMakeVaryMark(request, entry->getReply());
+ vary = httpMakeVaryMark(request, &reply);
if (!vary.isEmpty())
request->vary_headers = vary;
/* 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");
sendClientOldEntry();
}
- 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();
// origin replied 304
if (status == Http::scNotModified) {
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
} 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");
+ oldStatus << " to client");
sendClientOldEntry();
}
}
// origin replied with a non-error code
else if (status > Http::scNone && status < Http::scInternalServerError) {
- const HttpReply *new_rep = http->storeEntry()->getReply();
// 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");
+ oldStatus << ") to client");
sendClientOldEntry();
} else {
http->logType.update(LOG_TCP_REFRESH_MODIFIED);
// 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 (" << old_rep->sline.status() << ") to client");
+ status << ", sending old entry (" << oldStatus << ") to client");
sendClientOldEntry();
}
}
triggerInitialStoreRead();
if (http->redirect.status) {
- HttpReply *rep = new HttpReply;
+ const HttpReplyPointer rep(new HttpReply);
http->logType.update(LOG_TCP_REDIRECT);
http->storeEntry()->releaseRequest();
rep->redirect(http->redirect.status, http->redirect.location);
{
StoreEntry *const e = http->storeEntry();
- if (e->getReply()->sline.status() != Http::scOkay) {
- debugs(88, 4, "Reply code " << e->getReply()->sline.status() << " != 200");
+ 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().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
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();
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;
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;
{
assert(reply == NULL);
- reply = http->storeEntry()->getReply()->clone();
+ reply = http->storeEntry()->mem().freshestReply().clone();
HTTPMSGLOCK(reply);
http->al->reply = reply;
{
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)
virtual LogTags *loggingTags();
ClientHttpRequest *http;
+ /// Base reply header bytes received from Store.
+ /// Compatible with ClientHttpRequest::Out::offset.
+ /// Not to be confused with ClientHttpRequest::Out::headers_sz.
int headers_sz;
store_client *sc; /* The store_client we're using */
StoreIOBuffer tempBuffer; /* For use in validating requests via IMS */
bool
ClientHttpRequest::gotEnough() const
{
- /** TODO: should be querying the stream. */
+ // TODO: See also (and unify with) clientReplyContext::storeNotOKTransferDone()
int64_t contentLength =
- memObject()->getReply()->bodySize(request->method);
+ memObject()->baseReply().bodySize(request->method);
assert(contentLength >= 0);
if (out.offset < contentLength)
struct Out {
Out() : offset(0), size(0), headers_sz(0) {}
+ /// Roughly speaking, this offset points to the next body byte we want
+ /// to receive from Store. Without Ranges (and I/O errors), we should
+ /// have received (and written to the client) all the previous bytes.
+ /// XXX: The offset is updated by various receive-write steps, making
+ /// its exact meaning illusive. Its Out class placement is confusing.
int64_t offset;
+ /// Response header and body bytes written to the client connection.
uint64_t size;
+ /// Response header bytes written to the client connection.
+ /// Not to be confused with clientReplyContext::headers_sz.
size_t headers_sz;
} out;
// This relatively expensive check is not in StoreEntry::checkCachable:
// That method lacks HttpRequest and may be called too many times.
ACLFilledChecklist ch(acl, originalRequest().getRaw());
- ch.reply = const_cast<HttpReply*>(entry->getReply()); // ACLFilledChecklist API bug
+ ch.reply = const_cast<HttpReply*>(&entry->mem().freshestReply()); // ACLFilledChecklist API bug
HTTPMSGLOCK(ch.reply);
ch.al = fwd->al;
if (!ch.fastCheck().allowed()) { // when in doubt, block
IoState &rockWriter = dynamic_cast<IoState&>(*writer);
rockWriter.staleSplicingPointNext = staleSplicingPointNext;
+ // here, prefix is swap header plus HTTP reply header (i.e., updated bytes)
+ uint64_t stalePrefixSz = 0;
+ uint64_t freshPrefixSz = 0;
+
off_t offset = 0; // current writing offset (for debugging)
+ const auto &mem = update.entry->mem();
+
{
debugs(20, 7, "fresh store meta for " << *update.entry);
- const char *freshSwapHeader = update.entry->getSerialisedMetaData();
- const auto freshSwapHeaderSize = update.entry->mem_obj->swap_hdr_sz;
+ size_t freshSwapHeaderSize = 0; // set by getSerialisedMetaData() below
+
+ // There is a circular dependency between the correct/fresh value of
+ // entry->swap_file_sz and freshSwapHeaderSize. We break that loop by
+ // serializing zero swap_file_sz, just like the regular first-time
+ // swapout code may do. De-serializing code will re-calculate it in
+ // storeRebuildParseEntry(). TODO: Stop serializing entry->swap_file_sz.
+ const auto savedEntrySwapFileSize = update.entry->swap_file_sz;
+ update.entry->swap_file_sz = 0;
+ const auto freshSwapHeader = update.entry->getSerialisedMetaData(freshSwapHeaderSize);
+ update.entry->swap_file_sz = savedEntrySwapFileSize;
+
Must(freshSwapHeader);
writer->write(freshSwapHeader, freshSwapHeaderSize, 0, nullptr);
+ stalePrefixSz += mem.swap_hdr_sz;
+ freshPrefixSz += freshSwapHeaderSize;
offset += freshSwapHeaderSize;
xfree(freshSwapHeader);
}
{
debugs(20, 7, "fresh HTTP header @ " << offset);
- MemBuf *httpHeader = update.entry->mem_obj->getReply()->pack();
+ const auto httpHeader = mem.freshestReply().pack();
writer->write(httpHeader->content(), httpHeader->contentSize(), -1, nullptr);
+ const auto &staleReply = mem.baseReply();
+ Must(staleReply.hdr_sz >= 0); // for int-to-uint64_t conversion below
+ Must(staleReply.hdr_sz > 0); // already initialized
+ stalePrefixSz += staleReply.hdr_sz;
+ freshPrefixSz += httpHeader->contentSize();
offset += httpHeader->contentSize();
delete httpHeader;
}
exchangeBuffer.clear();
}
- debugs(20, 7, "wrote " << offset);
+ debugs(20, 7, "wrote " << offset <<
+ "; swap_file_sz delta: -" << stalePrefixSz << " +" << freshPrefixSz);
+
+ // Optimistic early update OK: Our write lock blocks access to swap_file_sz.
+ auto &swap_file_sz = update.fresh.anchor->basics.swap_file_sz;
+ Must(swap_file_sz >= stalePrefixSz);
+ swap_file_sz -= stalePrefixSz;
+ swap_file_sz += freshPrefixSz;
writer->close(StoreIOState::wroteAll); // should call noteDoneWriting()
}
if (!_peer->connection_auth)
return false;
- const HttpReplyPointer rep(entry->mem_obj->getReply());
+ const auto &rep = entry->mem().freshestReply();
/*The peer supports connection pinning and the http reply status
is not unauthorized, so the related connection can be pinned
*/
- if (rep->sline.status() != Http::scUnauthorized)
+ if (rep.sline.status() != Http::scUnauthorized)
return true;
/*The server respond with Http::scUnauthorized and the peer configured
reply and has in its list the "Session-Based-Authentication"
which means that the peer supports connection pinning.
*/
- if (rep->header.hasListMember(Http::HdrType::PROXY_SUPPORT, "Session-Based-Authentication", ','))
+ if (rep.header.hasListMember(Http::HdrType::PROXY_SUPPORT, "Session-Based-Authentication", ','))
return true;
return false;
if (StoreEntry *oldEntry = findPreviouslyCachedEntry(entry)) {
oldEntry->lock("HttpStateData::haveParsedReplyHeaders");
- sawDateGoBack = rep->olderThan(oldEntry->getReply());
+ sawDateGoBack = rep->olderThan(oldEntry->hasFreshestReply());
oldEntry->unlock("HttpStateData::haveParsedReplyHeaders");
}
}
else if (rep->content_length < 0)
range_err = "unknown length";
- else if (rep->content_length != http->memObject()->getReply()->content_length)
+ else if (rep->content_length != http->storeEntry()->mem().baseReply().content_length)
range_err = "INCONSISTENT length"; /* a bug? */
/* hits only - upstream CachePeer determines correct behaviour on misses,
* multi-range
*/
if (http->multipartRangeRequest() && i->debt() == i->currentSpec()->length) {
- assert(http->memObject());
clientPackRangeHdr(
- http->memObject()->getReply(), /* original reply */
+ &http->storeEntry()->mem().freshestReply(),
i->currentSpec(), /* current range */
i->boundary, /* boundary, the same for all */
mb);
double hops;
char *p;
int j;
- HttpReply const *rep;
size_t hdr_sz;
int nused = 0;
int size;
if ((hdr_sz = headersEnd(p, ex->buf_ofs))) {
debugs(38, 5, "netdbExchangeHandleReply: hdr_sz = " << hdr_sz);
- rep = ex->e->getReply();
- assert(rep->sline.status() != Http::scNone);
- debugs(38, 3, "netdbExchangeHandleReply: reply status " << rep->sline.status());
+ const auto scode = ex->e->mem().baseReply().sline.status();
+ assert(scode != Http::scNone);
+ debugs(38, 3, "netdbExchangeHandleReply: reply status " << scode);
- if (rep->sline.status() != Http::scOkay) {
+ if (scode != Http::scOkay) {
delete ex;
return;
}
update.stale.anchor->splicingPoint = update.stale.splicingPoint;
freeEntry(update.stale.fileNo);
- // make the stale anchor/chain reusable, reachable via its new location
+ // Make the stale anchor/chain reusable, reachable via update.fresh.name. If
+ // update.entry->swap_filen is still update.stale.fileNo, and the entry is
+ // using store, then the entry must have a lock on update.stale.fileNo,
+ // preventing its premature reuse by others.
relocate(update.fresh.name, update.stale.fileNo);
const Update updateSaved = update; // for post-close debugging below
StoreMapUpdate &operator =(const StoreMapUpdate &other) = delete;
StoreEntry *entry; ///< the store entry being updated
- Edition stale; ///< old anchor and chain being updated
- Edition fresh; ///< new anchor and updated chain prefix
+ Edition stale; ///< old anchor and chain
+ Edition fresh; ///< new anchor and the updated chain prefix
};
class StoreMapCleaner;
return -1;
if ((hdr_size = headersEnd(buf, size))) {
- HttpReply const *reply = fetch->entry->getReply();
- assert(reply);
- assert(reply->sline.status() != Http::scNone);
- const Http::StatusCode status = reply->sline.status();
+ const auto &reply = fetch->entry->mem().freshestReply();
+ const auto status = reply.sline.status();
+ assert(status != Http::scNone);
debugs(72, 3, "peerDigestFetchReply: " << pd->host << " status: " << status <<
- ", expires: " << (long int) reply->expires << " (" << std::showpos <<
- (int) (reply->expires - squid_curtime) << ")");
+ ", expires: " << (long int) reply.expires << " (" << std::showpos <<
+ (int) (reply.expires - squid_curtime) << ")");
/* this "if" is based on clientHandleIMSReply() */
}
} else {
/* some kind of a bug */
- peerDigestFetchAbort(fetch, buf, reply->sline.reason());
+ peerDigestFetchAbort(fetch, buf, reply.sline.reason());
return -1; /* XXX -1 will abort stuff in ReadReply! */
}
assert(!fetch->offset);
if ((hdr_size = headersEnd(buf, size))) {
- assert(fetch->entry->getReply());
- assert(fetch->entry->getReply()->sline.status() != Http::scNone);
+ const auto &reply = fetch->entry->mem().freshestReply();
+ const auto status = reply.sline.status();
+ assert(status != Http::scNone);
- if (fetch->entry->getReply()->sline.status() != Http::scOkay) {
+ if (status != Http::scOkay) {
debugs(72, DBG_IMPORTANT, "peerDigestSwapInHeaders: " << fetch->pd->host <<
- " status " << fetch->entry->getReply()->sline.status() <<
- " got cached!");
+ " status " << status << " got cached!");
peerDigestFetchAbort(fetch, buf, "internal status error");
return -1;
if (size >= (ssize_t)StoreDigestCBlockSize) {
PeerDigest *pd = fetch->pd;
- assert(pd && fetch->entry->getReply());
+ assert(pd);
+ assert(fetch->entry->mem_obj);
if (peerDigestSetCBlock(pd, buf)) {
/* XXX: soon we will have variable header size */
debugs(22, 3, "Staleness = " << staleness);
- const HttpReplyPointer reply(entry->mem_obj && entry->mem_obj->getReply() ? entry->mem_obj->getReply() : nullptr);
+ const auto reply = entry->hasFreshestReply(); // may be nil
// stale-if-error requires any failure be passed thru when its period is over.
int staleIfError = -1;
/* no mem_obj? */
return true;
- if (entry->getReply() == NULL)
- /* no reply? */
- return true;
-
- if (entry->getReply()->content_length == 0)
+ if (entry->mem_obj->baseReply().content_length == 0)
/* No use refreshing (caching?) 0 byte objects */
return false;
if (hasMemStore() && !shutting_down)
Store::Root().memoryDisconnect(*this);
- if (MemObject *mem = mem_obj) {
+ if (auto memObj = mem_obj) {
setMemStatus(NOT_IN_MEMORY);
mem_obj = NULL;
- delete mem;
+ delete memObj;
}
}
return nullptr;
HttpRequestPointer request(mem_obj->request);
+ const auto &reply = mem_obj->freshestReply();
if (mem_obj->vary_headers.isEmpty()) {
/* First handle the case where the object no longer varies */
/* Make sure the request knows the variance status */
if (request->vary_headers.isEmpty())
- request->vary_headers = httpMakeVaryMark(request.getRaw(), mem_obj->getReply().getRaw());
+ request->vary_headers = httpMakeVaryMark(request.getRaw(), &reply);
}
// TODO: storeGetPublic() calls below may create unlocked entries.
throw TexcHere("failed to make Vary marker public");
}
/* We are allowed to do this typecast */
- HttpReply *rep = new HttpReply;
+ const HttpReplyPointer rep(new HttpReply);
rep->setHeaders(Http::scOkay, "Internal marker object", "x-squid-internal/vary", -1, -1, squid_curtime + 100000);
- String vary = mem_obj->getReply()->header.getList(Http::HdrType::VARY);
+ auto vary = reply.header.getList(Http::HdrType::VARY);
if (vary.size()) {
/* Again, we own this structure layout */
}
#if X_ACCELERATOR_VARY
- vary = mem_obj->getReply()->header.getList(Http::HdrType::HDR_X_ACCELERATOR_VARY);
+ vary = reply.header.getList(Http::HdrType::HDR_X_ACCELERATOR_VARY);
if (vary.size() > 0) {
/* Again, we own this structure layout */
assert(store_status == STORE_PENDING);
// XXX: caller uses content offset, but we also store headers
- if (const HttpReplyPointer reply = mem_obj->getReply())
- writeBuffer.offset += reply->hdr_sz;
+ writeBuffer.offset += mem_obj->baseReply().hdr_sz;
debugs(20, 5, "storeWrite: writing " << writeBuffer.length << " bytes for '" << getMD5Text() << "'");
PROF_stop(StoreEntry_write);
* XXX sigh, offset might be < 0 here, but it gets "corrected"
* later. This offset crap is such a mess.
*/
- tempBuffer.offset = mem_obj->endOffset() - (getReply() ? getReply()->hdr_sz : 0);
+ tempBuffer.offset = mem_obj->endOffset() - mem_obj->baseReply().hdr_sz;
write(tempBuffer);
}
if (mem_obj->object_sz >= 0 &&
mem_obj->object_sz < Config.Store.minObjectSize)
return 1;
- if (getReply()->content_length > -1)
- if (getReply()->content_length < Config.Store.minObjectSize)
- return 1;
+
+ const auto clen = mem().baseReply().content_length;
+ if (clen >= 0 && clen < Config.Store.minObjectSize)
+ return 1;
return 0;
}
if (mem_obj->endOffset() > store_maxobjsize)
return true;
- if (getReply()->content_length < 0)
- return false;
-
- return (getReply()->content_length > store_maxobjsize);
+ const auto clen = mem_obj->baseReply().content_length;
+ return (clen >= 0 && clen > store_maxobjsize);
}
// TODO: move "too many open..." checks outside -- we are called too early/late
debugs(20, 3, "StoreEntry::checkCachable: NO: negative cached");
++store_check_cachable_hist.no.negative_cached;
return 0; /* avoid release call below */
- } else if (!mem_obj || !getReply()) {
+ } else if (!mem_obj) {
// XXX: In bug 4131, we forgetHit() without mem_obj, so we need
// this segfault protection, but how can we get such a HIT?
debugs(20, 2, "StoreEntry::checkCachable: NO: missing parts: " << *this);
return;
}
- /* This is suspect: mem obj offsets include the headers. do we adjust for that
- * in use of object_sz?
- */
mem_obj->object_sz = mem_obj->endOffset();
store_status = STORE_OK;
eventAdd("storeLateRelease", storeLateRelease, NULL, 0.0, 1);
}
+/// whether the base response has all the body bytes we expect
+/// \returns true for responses with unknown/unspecified body length
+/// \returns true for responses with the right number of accumulated body bytes
bool
StoreEntry::validLength() const
{
int64_t diff;
- const HttpReply *reply;
assert(mem_obj != NULL);
- reply = getReply();
+ const auto reply = &mem_obj->baseReply();
debugs(20, 3, "storeEntryValidLength: Checking '" << getMD5Text() << "'");
debugs(20, 5, "storeEntryValidLength: object_len = " <<
objectLen());
bool
StoreEntry::timestampsSet()
{
- const HttpReply *reply = getReply();
+ debugs(20, 7, *this << " had " << describeTimestamps());
+
+ // TODO: Remove change-reducing "&" before the official commit.
+ const auto reply = &mem().freshestReply();
+
time_t served_date = reply->date;
int age = reply->header.getInt(Http::HdrType::AGE);
/* Compute the timestamp, mimicking RFC2616 section 13.2.3. */
timestamp = served_date;
+ debugs(20, 5, *this << " has " << describeTimestamps());
+ return true;
+}
+
+bool
+StoreEntry::updateOnNotModified(const StoreEntry &e304)
+{
+ assert(mem_obj);
+ assert(e304.mem_obj);
+
+ // update reply before calling timestampsSet() below
+ const auto &oldReply = mem_obj->freshestReply();
+ const auto updatedReply = oldReply.recreateOnNotModified(e304.mem_obj->baseReply());
+ if (updatedReply) // HTTP 304 brought in new information
+ mem_obj->updateReply(*updatedReply);
+ // else continue to use the previous update, if any
+
+ if (!timestampsSet() && !updatedReply)
+ return false;
+
+ // Keep the old mem_obj->vary_headers; see HttpHeader::skipUpdateHeader().
+
+ debugs(20, 5, "updated basics in " << *this << " with " << e304);
+ mem_obj->appliedUpdates = true; // helps in triage; may already be true
return true;
}
}
}
-int64_t
-StoreEntry::objectLen() const
-{
- assert(mem_obj != NULL);
- return mem_obj->object_sz;
-}
-
-int64_t
-StoreEntry::contentLen() const
-{
- assert(mem_obj != NULL);
- assert(getReply() != NULL);
- return objectLen() - getReply()->hdr_sz;
-}
-
-HttpReply const *
-StoreEntry::getReply() const
-{
- return (mem_obj ? mem_obj->getReply().getRaw() : nullptr);
-}
-
void
StoreEntry::reset()
{
- assert (mem_obj);
debugs(20, 3, url());
- mem_obj->reset();
+ mem().reset();
expires = lastModified_ = timestamp = -1;
}
{
lock("StoreEntry::storeErrorResponse");
buffer();
- replaceHttpReply(reply);
+ replaceHttpReply(HttpReplyPointer(reply));
flush();
complete();
negativeCache();
* a new reply. This eats the reply.
*/
void
-StoreEntry::replaceHttpReply(HttpReply *rep, bool andStartWriting)
+StoreEntry::replaceHttpReply(const HttpReplyPointer &rep, const bool andStartWriting)
{
debugs(20, 3, "StoreEntry::replaceHttpReply: " << url());
return;
}
- mem_obj->replaceReply(HttpReplyPointer(rep));
+ mem_obj->replaceBaseReply(rep);
if (andStartWriting)
startWriting();
assert (isEmpty());
assert(mem_obj);
- const HttpReply *rep = getReply();
- assert(rep);
+ // Per MemObject replies definitions, we can only write our base reply.
+ // Currently, all callers replaceHttpReply() first, so there is no updated
+ // reply here anyway. Eventually, we may need to support the
+ // updateOnNotModified(),startWriting() sequence as well.
+ assert(!mem_obj->updatedReply());
+ const auto rep = &mem_obj->baseReply();
buffer();
rep->packHeadersUsingSlowPacker(*this);
}
char const *
-StoreEntry::getSerialisedMetaData()
+StoreEntry::getSerialisedMetaData(size_t &length) const
{
StoreMeta *tlv_list = storeSwapMetaBuild(this);
int swap_hdr_sz;
char *result = storeSwapMetaPack(tlv_list, &swap_hdr_sz);
storeSwapTLVFree(tlv_list);
assert (swap_hdr_sz >= 0);
- mem_obj->swap_hdr_sz = (size_t) swap_hdr_sz;
+ length = static_cast<size_t>(swap_hdr_sz);
return result;
}
bool
StoreEntry::modifiedSince(const time_t ims, const int imslen) const
{
- int object_length;
const time_t mod_time = lastModified();
debugs(88, 3, "modifiedSince: '" << url() << "'");
if (mod_time < 0)
return true;
- /* Find size of the object */
- object_length = getReply()->content_length;
-
- if (object_length < 0)
- object_length = contentLen();
+ assert(imslen < 0); // TODO: Either remove imslen or support it properly.
if (mod_time > ims) {
debugs(88, 3, "--> YES: entry newer than client");
} else if (mod_time < ims) {
debugs(88, 3, "--> NO: entry older than client");
return false;
- } else if (imslen < 0) {
- debugs(88, 3, "--> NO: same LMT, no client length");
- return false;
- } else if (imslen == object_length) {
- debugs(88, 3, "--> NO: same LMT, same length");
- return false;
} else {
- debugs(88, 3, "--> YES: same LMT, different length");
- return true;
+ debugs(88, 3, "--> NO: same LMT");
+ return false;
}
}
bool
StoreEntry::hasEtag(ETag &etag) const
{
- if (const HttpReply *reply = getReply()) {
+ if (const auto reply = hasFreshestReply()) {
etag = reply->header.getETag(Http::HdrType::ETAG);
if (etag.str)
return true;
bool
StoreEntry::hasOneOfEtags(const String &reqETags, const bool allowWeakMatch) const
{
- const ETag repETag = getReply()->header.getETag(Http::HdrType::ETAG);
+ const auto repETag = mem().freshestReply().header.getETag(Http::HdrType::ETAG);
if (!repETag.str) {
static SBuf asterisk("*", 1);
return strListIsMember(&reqETags, asterisk, ',');
}
void
-Store::Controller::updateOnNotModified(StoreEntry *old, const StoreEntry &newer)
+Store::Controller::updateOnNotModified(StoreEntry *old, StoreEntry &e304)
{
- /* update the old entry object */
Must(old);
- HttpReply *oldReply = const_cast<HttpReply*>(old->getReply());
- Must(oldReply);
+ Must(old->mem_obj);
+ Must(e304.mem_obj);
+
+ // updateOnNotModified() may be called many times for the same old entry.
+ // e304.mem_obj->appliedUpdates value distinguishes two cases:
+ // false: Independent store clients revalidating the same old StoreEntry.
+ // Each such update uses its own e304. The old StoreEntry
+ // accumulates such independent updates.
+ // true: Store clients feeding off the same 304 response. Each such update
+ // uses the same e304. For timestamps correctness and performance
+ // sake, it is best to detect and skip such repeated update calls.
+ if (e304.mem_obj->appliedUpdates) {
+ debugs(20, 5, "ignored repeated update of " << *old << " with " << e304);
+ return;
+ }
+ e304.mem_obj->appliedUpdates = true;
- const bool modified = oldReply->updateOnNotModified(newer.getReply());
- if (!old->timestampsSet() && !modified)
+ if (!old->updateOnNotModified(e304)) {
+ debugs(20, 5, "updated nothing in " << *old << " with " << e304);
return;
+ }
+
+ if (sharedMemStore && old->mem_status == IN_MEMORY && !EBIT_TEST(old->flags, ENTRY_SPECIAL))
+ sharedMemStore->updateHeaders(old);
- // XXX: Call memStore->updateHeaders(old) and swapDir->updateHeaders(old) to
- // update stored headers, stored metadata, and in-transit metadata.
- debugs(20, 3, *old << " headers were modified: " << modified);
+ if (old->swap_dirn > -1)
+ swapDir->updateHeaders(old);
}
bool
/// called to get rid of no longer needed entry data in RAM, if any
void memoryOut(StoreEntry &, const bool preserveSwappable);
- /// update old entry metadata and HTTP headers using a newer entry
- void updateOnNotModified(StoreEntry *old, const StoreEntry &newer);
+ /// using a 304 response, update the old entry (metadata and reply headers)
+ void updateOnNotModified(StoreEntry *old, StoreEntry &e304);
/// tries to make the entry available for collapsing future requests
bool allowCollapsing(StoreEntry *, const RequestFlags &, const HttpRequestMethod &);
if (len < 0)
return fail();
- if (copyInto.offset == 0 && len > 0 && entry->getReply()->sline.status() == Http::scNone) {
+ const auto rep = entry->mem_obj ? &entry->mem().baseReply() : nullptr;
+ if (copyInto.offset == 0 && len > 0 && rep && rep->sline.status() == Http::scNone) {
/* Our structure ! */
- HttpReply *rep = (HttpReply *) entry->getReply(); // bypass const
-
- if (!rep->parseCharBuf(copyInto.data, headersEnd(copyInto.data, len))) {
+ if (!entry->mem_obj->adjustableBaseReply().parseCharBuf(copyInto.data, headersEnd(copyInto.data, len))) {
debugs(90, DBG_CRITICAL, "Could not parse headers from on disk object");
} else {
parsed_header = 1;
}
}
- const HttpReply *rep = entry->getReply();
if (len > 0 && rep && entry->mem_obj->inmem_lo == 0 && entry->objectLen() <= (int64_t)Config.Store.maxInMemObjSize && Config.onoff.memory_cache_disk) {
storeGetMemSpace(len);
// The above may start to free our object so we need to check again
return true;
}
- int64_t expectlen = entry->getReply()->content_length + entry->getReply()->hdr_sz;
+ const auto &reply = mem->baseReply();
- if (expectlen < 0) {
- /* expectlen is < 0 if *no* information about the object has been received */
+ if (reply.hdr_sz <= 0) {
+ // TODO: Check whether this condition works for HTTP/0 responses.
debugs(90, 3, "quick-abort? YES no object data received yet");
return true;
}
- int64_t curlen = mem->endOffset();
-
if (Config.quickAbort.min < 0) {
debugs(90, 3, "quick-abort? NO disabled");
return false;
}
if (mem->request && mem->request->range && mem->request->getRangeOffsetLimit() < 0) {
- /* Don't abort if the admin has configured range_ofset -1 to download fully for caching. */
+ // the admin has configured "range_offset_limit none"
debugs(90, 3, "quick-abort? NO admin configured range replies to full-download");
return false;
}
+ if (reply.content_length < 0) {
+ // XXX: cf.data.pre does not document what should happen in this case
+ // We know that quick_abort is enabled, but no limit can be applied.
+ debugs(90, 3, "quick-abort? YES unknown content length");
+ return true;
+ }
+ const auto expectlen = reply.hdr_sz + reply.content_length;
+
+ int64_t curlen = mem->endOffset();
+
if (curlen > expectlen) {
debugs(90, 3, "quick-abort? YES bad content length (" << curlen << " of " << expectlen << " bytes received)");
return true;
return true;
}
+ // XXX: This is absurd! TODO: For positives, "a/(b/c) > d" is "a*c > b*d".
if (expectlen < 100) {
debugs(90, 3, "quick-abort? NO avoid FPE");
return false;
++storeLogTagsCounts[tag];
if (mem != NULL) {
- reply = e->getReply();
+ reply = &mem->freshestReply();
/*
* XXX Ok, where should we print the dir number here?
* Because if we print it before the swap file number, it'll break
* Build a TLV list for a StoreEntry
*/
tlv *
-storeSwapMetaBuild(StoreEntry * e)
+storeSwapMetaBuild(const StoreEntry *e)
{
tlv *TLV = NULL; /* we'll return this */
tlv **T = &TLV;
*/
// Create metadata now, possibly in vain: storeCreate needs swap_hdr_sz.
- const char *buf = e->getSerialisedMetaData ();
+ const auto buf = e->getSerialisedMetaData(mem->swap_hdr_sz);
assert(buf);
/* Create the swap file */
HttpHeader &HttpHeader::operator =(const HttpHeader &other) STUB_RETVAL(*this)
void HttpHeader::clean() STUB
void HttpHeader::append(const HttpHeader *) STUB
-bool HttpHeader::update(HttpHeader const *) STUB_RETVAL(false)
+void HttpHeader::update(const HttpHeader *) STUB
void HttpHeader::compact() STUB
int HttpHeader::parse(const char *, size_t, Http::ContentLengthInterpreter &) STUB_RETVAL(-1)
int HttpHeader::parse(const char *, size_t, bool, size_t &, Http::ContentLengthInterpreter &) STUB_RETVAL(-1)
void HttpReply::hdrCacheInit() STUB
HttpReply * HttpReply::clone() const STUB_RETVAL(NULL)
bool HttpReply::inheritProperties(const Http::Message *aMsg) STUB_RETVAL(false)
-bool HttpReply::updateOnNotModified(HttpReply const*) STUB_RETVAL(false)
+HttpReply::Pointer HttpReply::recreateOnNotModified(const HttpReply &) const STUB_RETVAL(nullptr)
int64_t HttpReply::bodySize(const HttpRequestMethod&) const STUB_RETVAL(0)
const HttpHdrContRange *HttpReply::contentRange() const STUB_RETVAL(nullptr)
void HttpReply::configureContentLengthInterpreter(Http::ContentLengthInterpreter &) STUB
void Controller::handleIdleEntry(StoreEntry &) STUB
void Controller::freeMemorySpace(const int) STUB
void Controller::memoryOut(StoreEntry &, const bool) STUB
-void Controller::updateOnNotModified(StoreEntry *, const StoreEntry &) STUB
+void Controller::updateOnNotModified(StoreEntry *, StoreEntry &) STUB
bool Controller::allowCollapsing(StoreEntry *, const RequestFlags &, const HttpRequestMethod &) STUB_RETVAL(false)
void Controller::addReading(StoreEntry *, const cache_key *) STUB
void Controller::addWriting(StoreEntry *, const cache_key *) STUB
const char *StoreEntry::getMD5Text() const STUB_RETVAL(NULL)
StoreEntry::StoreEntry() STUB
StoreEntry::~StoreEntry() STUB
-HttpReply const *StoreEntry::getReply() const STUB_RETVAL(NULL)
void StoreEntry::write(StoreIOBuffer) STUB
bool StoreEntry::isAccepting() const STUB_RETVAL(false)
size_t StoreEntry::bytesWanted(Range<size_t> const, bool) const STUB_RETVAL(0)
void StoreEntry::complete() STUB
store_client_t StoreEntry::storeClientType() const STUB_RETVAL(STORE_NON_CLIENT)
-char const *StoreEntry::getSerialisedMetaData() STUB_RETVAL(NULL)
-void StoreEntry::replaceHttpReply(HttpReply *, bool andStartWriting) STUB
+char const *StoreEntry::getSerialisedMetaData(size_t &length) const STUB_RETVAL(NULL)
+void StoreEntry::replaceHttpReply(const HttpReplyPointer &, bool andStartWriting) STUB
bool StoreEntry::mayStartSwapOut() STUB_RETVAL(false)
void StoreEntry::trimMemory(const bool preserveSwappable) STUB
void StoreEntry::abort() STUB
void StoreEntry::buffer() STUB
void StoreEntry::flush() STUB
int StoreEntry::unlock(const char *) STUB_RETVAL(0)
-int64_t StoreEntry::objectLen() const STUB_RETVAL(0)
-int64_t StoreEntry::contentLen() const STUB_RETVAL(0)
void StoreEntry::lock(const char *) STUB
void StoreEntry::touch() STUB
void StoreEntry::release(const bool shareable) STUB
void storeUnlink(StoreEntry * e) STUB
char *storeSwapMetaPack(tlv * tlv_list, int *length) STUB_RETVAL(NULL)
-tlv *storeSwapMetaBuild(StoreEntry * e) STUB_RETVAL(NULL)
+tlv *storeSwapMetaBuild(const StoreEntry *) STUB_RETVAL(nullptr)
void storeSwapTLVFree(tlv * n) STUB
flags.cachable = true;
StoreEntry *const pe =
storeCreateEntry(storeId(i), "dummy log url", flags, Http::METHOD_GET);
- HttpReply *const rep = const_cast<HttpReply *>(pe->getReply());
- rep->setHeaders(Http::scOkay, "dummy test object", "x-squid-internal/test", 0, -1, squid_curtime + 100000);
+ auto &rep = pe->mem().adjustableBaseReply();
+ rep.setHeaders(Http::scOkay, "dummy test object", "x-squid-internal/test", 0, -1, squid_curtime + 100000);
pe->setPublicKey();
StoreEntry *const pe = createEntry(i);
pe->buffer();
- pe->getReply()->packHeadersUsingSlowPacker(*pe);
+ pe->mem().freshestReply().packHeadersUsingSlowPacker(*pe);
pe->flush();
pe->timestampsSet();
pe->complete();
RequestFlags flags;
flags.cachable = true;
StoreEntry *pe = storeCreateEntry("dummy url", "dummy log url", flags, Http::METHOD_GET);
- HttpReply *rep = (HttpReply *) pe->getReply(); // bypass const
- rep->setHeaders(Http::scOkay, "dummy test object", "x-squid-internal/test", 0, -1, squid_curtime + 100000);
+ auto &reply = pe->mem().adjustableBaseReply();
+ reply.setHeaders(Http::scOkay, "dummy test object", "x-squid-internal/test", 0, -1, squid_curtime + 100000);
pe->setPublicKey();
pe->buffer();
- pe->getReply()->packHeadersUsingSlowPacker(*pe);
+ pe->mem().freshestReply().packHeadersUsingSlowPacker(*pe);
pe->flush();
pe->timestampsSet();
pe->complete();
}
s = buf + k;
- assert(urlres_e->getReply());
+ // TODO: Check whether we should parse urlres_e reply, as before 528b2c61.
rep = new HttpReply;
rep->parseCharBuf(buf, k);
debugs(52, 3, "reply exists, code=" << rep->sline.status() << ".");