2 * Copyright (C) 1996-2022 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
10 #include "acl/FilledChecklist.h"
11 #include "acl/Gadgets.h"
12 #include "base/TextException.h"
13 #include "clients/Client.h"
14 #include "comm/Connection.h"
15 #include "comm/forward.h"
16 #include "comm/Write.h"
17 #include "error/Detail.h"
18 #include "errorpage.h"
20 #include "HttpHdrContRange.h"
21 #include "HttpReply.h"
22 #include "HttpRequest.h"
23 #include "SquidConfig.h"
24 #include "StatCounters.h"
29 #include "adaptation/AccessCheck.h"
30 #include "adaptation/Answer.h"
31 #include "adaptation/Iterator.h"
32 #include "base/AsyncCall.h"
35 // implemented in client_side_reply.cc until sides have a common parent
36 void purgeEntriesByUrl(HttpRequest
* req
, const char *url
);
38 Client::Client(FwdState
*theFwdState
) :
44 entry
->lock("Client");
49 // paranoid: check that swanSong has been called
50 assert(!requestBodySource
);
52 assert(!virginBodyDestination
);
53 assert(!adaptedBodySource
);
56 entry
->unlock("Client");
58 HTTPMSGUNLOCK(theVirginReply
);
59 HTTPMSGUNLOCK(theFinalReply
);
61 if (responseBodyBuffer
!= nullptr) {
62 delete responseBodyBuffer
;
63 responseBodyBuffer
= nullptr;
70 // get rid of our piping obligations
71 if (requestBodySource
!= nullptr)
72 stopConsumingFrom(requestBodySource
);
78 if (!doneWithServer())
82 doneWithFwd
= "swanSong()";
83 fwd
->handleUnregisteredServerEnd();
86 BodyConsumer::swanSong();
88 Initiator::swanSong();
89 BodyProducer::swanSong();
92 // paranoid: check that swanSong has been called
93 // extra paranoid: yeah, I really mean it. they MUST pass here.
94 assert(!requestBodySource
);
96 assert(!virginBodyDestination
);
97 assert(!adaptedBodySource
);
102 Client::virginReply()
104 assert(theVirginReply
);
105 return theVirginReply
;
109 Client::virginReply() const
111 assert(theVirginReply
);
112 return theVirginReply
;
116 Client::setVirginReply(HttpReply
*rep
)
118 debugs(11,5, this << " setting virgin reply to " << rep
);
119 assert(!theVirginReply
);
121 theVirginReply
= rep
;
122 HTTPMSGLOCK(theVirginReply
);
124 fwd
->al
->reply
= theVirginReply
;
125 return theVirginReply
;
131 assert(theFinalReply
);
132 return theFinalReply
;
136 Client::setFinalReply(HttpReply
*rep
)
138 debugs(11,5, this << " setting final reply to " << rep
);
140 assert(!theFinalReply
);
143 HTTPMSGLOCK(theFinalReply
);
145 fwd
->al
->reply
= theFinalReply
;
147 // give entry the reply because haveParsedReplyHeaders() expects it there
148 entry
->replaceHttpReply(theFinalReply
, false); // but do not write yet
149 haveParsedReplyHeaders(); // update the entry/reply (e.g., set timestamps)
150 if (!EBIT_TEST(entry
->flags
, RELEASE_REQUEST
) && blockCaching())
152 entry
->startWriting(); // write the updated entry to store
154 return theFinalReply
;
158 Client::markParsedVirginReplyAsWhole(const char *reasonWeAreSure
)
160 assert(reasonWeAreSure
);
161 debugs(11, 3, reasonWeAreSure
);
163 // The code storing adapted reply takes care of markStoredReplyAsWhole().
164 // We need to take care of the remaining regular network-to-store case.
166 if (startedAdaptation
) {
167 debugs(11, 5, "adaptation handles markStoredReplyAsWhole()");
172 // Convert the "parsed whole virgin reply" event into the "stored..." event
173 // because, without adaptation, we store everything we parse: There is no
174 // buffer for parsed content; addVirginReplyBody() stores every parsed byte.
175 fwd
->markStoredReplyAsWhole(reasonWeAreSure
);
178 // called when no more server communication is expected; may quit
180 Client::serverComplete()
182 debugs(11,5, "serverComplete " << this);
184 if (!doneWithServer()) {
186 assert(doneWithServer());
190 originalRequest()->hier
.stopPeerClock(true);
192 if (requestBodySource
!= nullptr)
193 stopConsumingFrom(requestBodySource
);
195 if (responseBodyBuffer
!= nullptr)
202 Client::serverComplete2()
204 debugs(11,5, "serverComplete2 " << this);
207 if (virginBodyDestination
!= nullptr)
208 stopProducingFor(virginBodyDestination
, true);
210 if (!doneWithAdaptation())
214 completeForwarding();
217 bool Client::doneAll() const
219 return doneWithServer() &&
221 doneWithAdaptation() &&
222 Adaptation::Initiator::doneAll() &&
223 BodyProducer::doneAll() &&
225 BodyConsumer::doneAll();
228 // FTP side overloads this to work around multiple calls to fwd->complete
230 Client::completeForwarding()
232 debugs(11,5, "completing forwarding for " << fwd
);
233 assert(fwd
!= nullptr);
234 doneWithFwd
= "completeForwarding()";
238 // Register to receive request body
239 bool Client::startRequestBodyFlow()
241 HttpRequestPointer
r(originalRequest());
242 assert(r
->body_pipe
!= nullptr);
243 requestBodySource
= r
->body_pipe
;
244 if (requestBodySource
->setConsumerIfNotLate(this)) {
245 debugs(11,3, "expecting request body from " <<
246 requestBodySource
->status());
250 debugs(11,3, "aborting on partially consumed request body: " <<
251 requestBodySource
->status());
252 requestBodySource
= nullptr;
256 // Entry-dependent callbacks use this check to quit if the entry went bad
258 Client::abortOnBadEntry(const char *abortReason
)
260 if (entry
->isAccepting())
263 debugs(11,5, "entry is not Accepting!");
264 abortOnData(abortReason
);
268 // more request or adapted response body is available
270 Client::noteMoreBodyDataAvailable(BodyPipe::Pointer bp
)
273 if (adaptedBodySource
== bp
) {
274 handleMoreAdaptedBodyAvailable();
278 if (requestBodySource
== bp
)
279 handleMoreRequestBodyAvailable();
282 // the entire request or adapted response body was provided, successfully
284 Client::noteBodyProductionEnded(BodyPipe::Pointer bp
)
287 if (adaptedBodySource
== bp
) {
288 handleAdaptedBodyProductionEnded();
292 if (requestBodySource
== bp
)
293 handleRequestBodyProductionEnded();
296 // premature end of the request or adapted response body production
298 Client::noteBodyProducerAborted(BodyPipe::Pointer bp
)
301 if (adaptedBodySource
== bp
) {
302 handleAdaptedBodyProducerAborted();
306 if (requestBodySource
== bp
)
307 handleRequestBodyProducerAborted();
311 Client::abortOnData(const char *reason
)
317 // more origin request body data is available
319 Client::handleMoreRequestBodyAvailable()
322 sendMoreRequestBody();
324 debugs(9,3, "waiting for request body write to complete");
327 // there will be no more handleMoreRequestBodyAvailable calls
329 Client::handleRequestBodyProductionEnded()
331 receivedWholeRequestBody
= true;
333 doneSendingRequestBody();
335 debugs(9,3, "waiting for request body write to complete");
338 // called when we are done sending request body; kids extend this
340 Client::doneSendingRequestBody()
342 debugs(9,3, "done sending request body");
343 assert(requestBodySource
!= nullptr);
344 stopConsumingFrom(requestBodySource
);
349 // called when body producers aborts; kids extend this
351 Client::handleRequestBodyProducerAborted()
353 if (requestSender
!= nullptr)
354 debugs(9,3, "fyi: request body aborted while we were sending");
356 fwd
->dontRetry(true); // the problem is not with the server
357 stopConsumingFrom(requestBodySource
); // requestSender, if any, will notice
362 // called when we wrote request headers(!) or a part of the body
364 Client::sentRequestBody(const CommIoCbParams
&io
)
366 debugs(11, 5, "sentRequestBody: FD " << io
.fd
<< ": size " << io
.size
<< ": errflag " << io
.flag
<< ".");
367 debugs(32,3, "sentRequestBody called");
369 requestSender
= nullptr;
372 fd_bytes(io
.fd
, io
.size
, FD_WRITE
);
373 statCounter
.server
.all
.kbytes_out
+= io
.size
;
374 // kids should increment their counters
377 if (io
.flag
== Comm::ERR_CLOSING
)
380 if (!requestBodySource
) {
381 debugs(9,3, "detected while-we-were-sending abort");
382 return; // do nothing;
385 // both successful and failed writes affect response times
386 request
->hier
.notePeerWrite();
389 debugs(11, DBG_IMPORTANT
, "ERROR: sentRequestBody failure: FD " << io
.fd
<< ": " << xstrerr(io
.xerrno
));
391 err
= new ErrorState(ERR_WRITE_ERROR
, Http::scBadGateway
, fwd
->request
, fwd
->al
);
392 err
->xerrno
= io
.xerrno
;
394 abortOnData("I/O error while sending request body");
398 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
399 abortOnData("store entry aborted while sending request body");
403 if (!requestBodySource
->exhausted())
404 sendMoreRequestBody();
405 else if (receivedWholeRequestBody
)
406 doneSendingRequestBody();
408 debugs(9,3, "waiting for body production end or abort");
412 Client::sendMoreRequestBody()
414 assert(requestBodySource
!= nullptr);
415 assert(!requestSender
);
417 const Comm::ConnectionPointer conn
= dataConnection();
419 if (!Comm::IsConnOpen(conn
)) {
420 debugs(9,3, "cannot send request body to closing " << conn
);
421 return; // wait for the kid's close handler; TODO: assert(closer);
425 if (getMoreRequestBody(buf
) && buf
.contentSize() > 0) {
426 debugs(9,3, "will write " << buf
.contentSize() << " request body bytes");
427 typedef CommCbMemFunT
<Client
, CommIoCbParams
> Dialer
;
428 requestSender
= JobCallback(93,3, Dialer
, this, Client::sentRequestBody
);
429 Comm::Write(conn
, &buf
, requestSender
);
431 debugs(9,3, "will wait for more request body bytes or eof");
432 requestSender
= nullptr;
436 /// either fill buf with available [encoded] request body bytes or return false
438 Client::getMoreRequestBody(MemBuf
&buf
)
440 // default implementation does not encode request body content
441 Must(requestBodySource
!= nullptr);
442 return requestBodySource
->getMoreData(buf
);
445 // Compares hosts in urls, returns false if different, no sheme, or no host.
447 sameUrlHosts(const char *url1
, const char *url2
)
449 // XXX: Want AnyP::Uri::parse() here, but it uses static storage and copying
450 const char *host1
= strchr(url1
, ':');
451 const char *host2
= strchr(url2
, ':');
453 if (host1
&& host2
) {
454 // skip scheme slashes
458 } while (*host1
== '/' && *host2
== '/');
461 return false; // no host
463 // increment while the same until we reach the end of the URL/host
464 while (*host1
&& *host1
!= '/' && *host1
== *host2
) {
468 return *host1
== *host2
;
471 return false; // no URL scheme
474 // purges entries that match the value of a given HTTP [response] header
476 purgeEntriesByHeader(HttpRequest
*req
, const char *reqUrl
, Http::Message
*rep
, Http::HdrType hdr
)
478 const auto hdrUrl
= rep
->header
.getStr(hdr
);
483 * If the URL is relative, make it absolute so we can find it.
484 * If it's absolute, make sure the host parts match to avoid DOS attacks
485 * as per RFC 2616 13.10.
488 const char *absUrl
= nullptr;
489 if (urlIsRelative(hdrUrl
)) {
490 if (req
->method
.id() == Http::METHOD_CONNECT
)
491 absUrl
= hdrUrl
; // TODO: merge authority-uri and hdrUrl
492 else if (req
->url
.getScheme() == AnyP::PROTO_URN
)
493 absUrl
= req
->url
.absolute().c_str();
495 AnyP::Uri tmpUrl
= req
->url
;
496 if (*hdrUrl
== '/') {
497 // RFC 3986 section 4.2: absolute-path reference
498 // for this logic replace the entire request-target URI path
501 tmpUrl
.addRelativePath(reqUrl
);
503 absUrlMaker
= tmpUrl
.absolute();
504 absUrl
= absUrlMaker
.c_str();
506 } else if (!sameUrlHosts(reqUrl
, hdrUrl
)) {
511 purgeEntriesByUrl(req
, absUrl
);
514 // some HTTP methods should purge matching cache entries
516 Client::maybePurgeOthers()
518 // only some HTTP methods should purge matching cache entries
519 if (!request
->method
.purgesOthers())
522 // and probably only if the response was successful
523 if (theFinalReply
->sline
.status() >= 400)
526 // XXX: should we use originalRequest() here?
527 SBuf
tmp(request
->effectiveRequestUri());
528 const char *reqUrl
= tmp
.c_str();
529 debugs(88, 5, "maybe purging due to " << request
->method
<< ' ' << tmp
);
530 purgeEntriesByUrl(request
.getRaw(), reqUrl
);
531 purgeEntriesByHeader(request
.getRaw(), reqUrl
, theFinalReply
, Http::HdrType::LOCATION
);
532 purgeEntriesByHeader(request
.getRaw(), reqUrl
, theFinalReply
, Http::HdrType::CONTENT_LOCATION
);
535 /// called when we have final (possibly adapted) reply headers; kids extend
537 Client::haveParsedReplyHeaders()
542 // adaptation may overwrite old offset computed using the virgin response
544 if (const auto cr
= theFinalReply
->contentRange()) {
545 if (cr
->spec
.offset
!= HttpHdrRangeSpec::UnknownPosition
)
546 currentOffset
= cr
->spec
.offset
;
550 /// whether to prevent caching of an otherwise cachable response
552 Client::blockCaching()
554 if (const Acl::Tree
*acl
= Config
.accessList
.storeMiss
) {
555 // This relatively expensive check is not in StoreEntry::checkCachable:
556 // That method lacks HttpRequest and may be called too many times.
557 ACLFilledChecklist
ch(acl
, originalRequest().getRaw());
558 ch
.reply
= const_cast<HttpReply
*>(&entry
->mem().freshestReply()); // ACLFilledChecklist API bug
559 HTTPMSGLOCK(ch
.reply
);
561 if (!ch
.fastCheck().allowed()) { // when in doubt, block
562 debugs(20, 3, "store_miss prohibits caching");
570 Client::originalRequest()
576 /// Initiate an asynchronous adaptation transaction which will call us back.
578 Client::startAdaptation(const Adaptation::ServiceGroupPointer
&group
, HttpRequest
*cause
)
580 debugs(11, 5, "Client::startAdaptation() called");
581 // check whether we should be sending a body as well
582 // start body pipe to feed ICAP transaction if needed
583 assert(!virginBodyDestination
);
584 HttpReply
*vrep
= virginReply();
585 assert(!vrep
->body_pipe
);
587 if (vrep
->expectingBody(cause
->method
, size
) && size
) {
588 virginBodyDestination
= new BodyPipe(this);
589 vrep
->body_pipe
= virginBodyDestination
;
590 debugs(93, 6, "will send virgin reply body to " <<
591 virginBodyDestination
<< "; size: " << size
);
593 virginBodyDestination
->setBodySize(size
);
596 adaptedHeadSource
= initiateAdaptation(
597 new Adaptation::Iterator(vrep
, cause
, fwd
->al
, group
));
598 startedAdaptation
= initiated(adaptedHeadSource
);
599 Must(startedAdaptation
);
602 // properly cleans up ICAP-related state
603 // may be called multiple times
604 void Client::cleanAdaptation()
606 debugs(11,5, "cleaning ICAP; ACL: " << adaptationAccessCheckPending
);
608 if (virginBodyDestination
!= nullptr)
609 stopProducingFor(virginBodyDestination
, false);
611 announceInitiatorAbort(adaptedHeadSource
);
613 if (adaptedBodySource
!= nullptr)
614 stopConsumingFrom(adaptedBodySource
);
616 if (!adaptationAccessCheckPending
) // we cannot cancel a pending callback
617 assert(doneWithAdaptation()); // make sure the two methods are in sync
621 Client::doneWithAdaptation() const
623 return !adaptationAccessCheckPending
&&
624 !virginBodyDestination
&& !adaptedHeadSource
&& !adaptedBodySource
;
627 // sends virgin reply body to ICAP, buffering excesses if needed
629 Client::adaptVirginReplyBody(const char *data
, ssize_t len
)
631 assert(startedAdaptation
);
633 if (!virginBodyDestination
) {
634 debugs(11,3, "ICAP does not want more virgin body");
638 // grow overflow area if already overflowed
639 if (responseBodyBuffer
) {
640 responseBodyBuffer
->append(data
, len
);
641 data
= responseBodyBuffer
->content();
642 len
= responseBodyBuffer
->contentSize();
645 const ssize_t putSize
= virginBodyDestination
->putMoreData(data
, len
);
649 // if we had overflow area, shrink it as necessary
650 if (responseBodyBuffer
) {
651 if (putSize
== responseBodyBuffer
->contentSize()) {
652 delete responseBodyBuffer
;
653 responseBodyBuffer
= nullptr;
655 responseBodyBuffer
->consume(putSize
);
660 // if we did not have an overflow area, create it as needed
662 assert(!responseBodyBuffer
);
663 responseBodyBuffer
= new MemBuf
;
664 responseBodyBuffer
->init(4096, SQUID_TCP_SO_RCVBUF
* 10);
665 responseBodyBuffer
->append(data
, len
);
669 // can supply more virgin response body data
671 Client::noteMoreBodySpaceAvailable(BodyPipe::Pointer
)
673 if (responseBodyBuffer
) {
674 addVirginReplyBody(nullptr, 0); // kick the buffered fragment alive again
675 if (completed
&& !responseBodyBuffer
) {
680 maybeReadVirginBody();
683 // the consumer of our virgin response body aborted
685 Client::noteBodyConsumerAborted(BodyPipe::Pointer
)
687 stopProducingFor(virginBodyDestination
, false);
689 // do not force closeServer here in case we need to bypass AdaptationQueryAbort
691 if (doneWithAdaptation()) // we may still be receiving adapted response
692 handleAdaptationCompleted();
695 // received adapted response headers (body may follow)
697 Client::noteAdaptationAnswer(const Adaptation::Answer
&answer
)
699 clearAdaptation(adaptedHeadSource
); // we do not expect more messages
701 switch (answer
.kind
) {
702 case Adaptation::Answer::akForward
:
703 handleAdaptedHeader(const_cast<Http::Message
*>(answer
.message
.getRaw()));
706 case Adaptation::Answer::akBlock
:
707 handleAdaptationBlocked(answer
);
710 case Adaptation::Answer::akError
:
711 handleAdaptationAborted(!answer
.final
);
717 Client::handleAdaptedHeader(Http::Message
*msg
)
719 if (abortOnBadEntry("entry went bad while waiting for adapted headers")) {
720 // If the adapted response has a body, the ICAP side needs to know
721 // that nobody will consume that body. We will be destroyed upon
722 // return. Tell the ICAP side that it is on its own.
723 HttpReply
*rep
= dynamic_cast<HttpReply
*>(msg
);
725 if (rep
->body_pipe
!= nullptr)
726 rep
->body_pipe
->expectNoConsumption();
731 HttpReply
*rep
= dynamic_cast<HttpReply
*>(msg
);
733 debugs(11,5, this << " setting adapted reply to " << rep
);
736 assert(!adaptedBodySource
);
737 if (rep
->body_pipe
!= nullptr) {
738 // subscribe to receive adapted body
739 adaptedBodySource
= rep
->body_pipe
;
740 // assume that ICAP does not auto-consume on failures
741 const bool result
= adaptedBodySource
->setConsumerIfNotLate(this);
745 fwd
->markStoredReplyAsWhole("setFinalReply() stored header-only adapted reply");
746 if (doneWithAdaptation()) // we may still be sending virgin response
747 handleAdaptationCompleted();
752 Client::resumeBodyStorage()
754 if (abortOnBadEntry("store entry aborted while kick producer callback"))
757 if (!adaptedBodySource
)
760 handleMoreAdaptedBodyAvailable();
762 if (adaptedBodySource
!= nullptr && adaptedBodySource
->exhausted())
763 endAdaptedBodyConsumption();
766 // more adapted response body is available
768 Client::handleMoreAdaptedBodyAvailable()
770 if (abortOnBadEntry("entry refuses adapted body"))
775 size_t contentSize
= adaptedBodySource
->buf().contentSize();
778 return; // XXX: bytesWanted asserts on zero-size ranges
780 const size_t spaceAvailable
= entry
->bytesWanted(Range
<size_t>(0, contentSize
), true);
782 if (spaceAvailable
< contentSize
) {
783 // No or partial body data consuming
784 typedef NullaryMemFunT
<Client
> Dialer
;
785 AsyncCall::Pointer call
= asyncCall(93, 5, "Client::resumeBodyStorage",
786 Dialer(this, &Client::resumeBodyStorage
));
787 entry
->deferProducer(call
);
790 if (!spaceAvailable
) {
791 debugs(11, 5, "NOT storing " << contentSize
<< " bytes of adapted " <<
792 "response body at offset " << adaptedBodySource
->consumedSize());
796 if (spaceAvailable
< contentSize
) {
797 debugs(11, 5, "postponing storage of " <<
798 (contentSize
- spaceAvailable
) << " body bytes");
799 contentSize
= spaceAvailable
;
802 debugs(11,5, "storing " << contentSize
<< " bytes of adapted " <<
803 "response body at offset " << adaptedBodySource
->consumedSize());
805 BodyPipeCheckout
bpc(*adaptedBodySource
);
806 const StoreIOBuffer
ioBuf(&bpc
.buf
, currentOffset
, contentSize
);
807 currentOffset
+= ioBuf
.length
;
809 bpc
.buf
.consume(contentSize
);
813 // the entire adapted response body was produced, successfully
815 Client::handleAdaptedBodyProductionEnded()
817 if (abortOnBadEntry("entry went bad while waiting for adapted body eof"))
820 // distinguish this code path from handleAdaptedBodyProducerAborted()
821 receivedWholeAdaptedReply
= true;
823 // end consumption if we consumed everything
824 if (adaptedBodySource
!= nullptr && adaptedBodySource
->exhausted())
825 endAdaptedBodyConsumption();
826 // else resumeBodyStorage() will eventually consume the rest
830 Client::endAdaptedBodyConsumption()
832 stopConsumingFrom(adaptedBodySource
);
834 if (receivedWholeAdaptedReply
) {
835 // We received the entire adapted reply per receivedWholeAdaptedReply.
836 // We are called when we consumed everything received (per our callers).
837 // We consume only what we store per handleMoreAdaptedBodyAvailable().
838 fwd
->markStoredReplyAsWhole("received,consumed=>stored the entire RESPMOD reply");
841 handleAdaptationCompleted();
844 // premature end of the adapted response body
845 void Client::handleAdaptedBodyProducerAborted()
847 if (abortOnBadEntry("entry went bad while waiting for the now-aborted adapted body"))
850 Must(adaptedBodySource
!= nullptr);
851 if (!adaptedBodySource
->exhausted()) {
852 debugs(11,5, "waiting to consume the remainder of the aborted adapted body");
853 return; // resumeBodyStorage() should eventually consume the rest
856 stopConsumingFrom(adaptedBodySource
);
858 if (handledEarlyAdaptationAbort())
861 handleAdaptationCompleted(); // the user should get a truncated response
864 // common part of noteAdaptationAnswer and handleAdaptedBodyProductionEnded
866 Client::handleAdaptationCompleted()
868 debugs(11,5, "handleAdaptationCompleted");
871 // We stop reading origin response because we have no place to put it(*) and
872 // cannot use it. If some origin servers do not like that or if we want to
873 // reuse more pconns, we can add code to discard unneeded origin responses.
874 // (*) TODO: Is it possible that the adaptation xaction is still running?
875 if (mayReadVirginReplyBody()) {
876 debugs(11,3, "closing origin conn due to ICAP completion");
880 completeForwarding();
883 // common part of noteAdaptation*Aborted and noteBodyConsumerAborted methods
885 Client::handleAdaptationAborted(bool bypassable
)
887 debugs(11,5, "handleAdaptationAborted; bypassable: " << bypassable
<<
888 ", entry empty: " << entry
->isEmpty());
890 if (abortOnBadEntry("entry went bad while ICAP aborted"))
893 // TODO: bypass if possible
894 if (!handledEarlyAdaptationAbort())
895 abortAll("adaptation failure with a filled entry");
898 /// If the store entry is still empty, fully handles adaptation abort, returning
899 /// true. Otherwise just updates the request error detail and returns false.
901 Client::handledEarlyAdaptationAbort()
903 if (entry
->isEmpty()) {
904 debugs(11,8, "adaptation failure with an empty entry: " << *entry
);
905 const auto err
= new ErrorState(ERR_ICAP_FAILURE
, Http::scInternalServerError
, request
.getRaw(), fwd
->al
);
906 static const auto d
= MakeNamedErrorDetail("ICAP_RESPMOD_EARLY");
909 fwd
->dontRetry(true);
910 abortAll("adaptation failure with an empty entry");
911 return true; // handled
914 if (request
) { // update logged info directly
915 static const auto d
= MakeNamedErrorDetail("ICAP_RESPMOD_LATE");
916 request
->detailError(ERR_ICAP_FAILURE
, d
);
919 return false; // the caller must handle
922 // adaptation service wants us to deny HTTP client access to this response
924 Client::handleAdaptationBlocked(const Adaptation::Answer
&answer
)
926 debugs(11,5, answer
.ruleId
);
928 if (abortOnBadEntry("entry went bad while ICAP aborted"))
931 if (!entry
->isEmpty()) { // too late to block (should not really happen)
933 static const auto d
= MakeNamedErrorDetail("RESPMOD_BLOCK_LATE");
934 request
->detailError(ERR_ICAP_FAILURE
, d
);
936 abortAll("late adaptation block");
940 debugs(11,7, "creating adaptation block response");
943 aclGetDenyInfoPage(&Config
.denyInfoList
, answer
.ruleId
.termedBuf(), 1);
944 if (page_id
== ERR_NONE
)
945 page_id
= ERR_ACCESS_DENIED
;
947 const auto err
= new ErrorState(page_id
, Http::scForbidden
, request
.getRaw(), fwd
->al
);
948 static const auto d
= MakeNamedErrorDetail("RESPMOD_BLOCK_EARLY");
951 fwd
->dontRetry(true);
953 abortOnData("timely adaptation block");
957 Client::noteAdaptationAclCheckDone(Adaptation::ServiceGroupPointer group
)
959 adaptationAccessCheckPending
= false;
961 if (abortOnBadEntry("entry went bad while waiting for ICAP ACL check"))
964 // TODO: Should non-ICAP and ICAP REPMOD pre-cache paths check this?
965 // That check now only happens on REQMOD pre-cache and REPMOD post-cache, in processReplyAccess().
966 if (virginReply()->expectedBodyTooLarge(*request
)) {
967 sendBodyIsTooLargeError();
970 // TODO: Should we check receivedBodyTooLarge as well?
973 debugs(11,3, "no adapation needed");
974 setFinalReply(virginReply());
979 startAdaptation(group
, originalRequest().getRaw());
985 Client::sendBodyIsTooLargeError()
987 const auto err
= new ErrorState(ERR_TOO_BIG
, Http::scForbidden
, request
.getRaw(), fwd
->al
);
989 fwd
->dontRetry(true);
990 abortOnData("Virgin body too large.");
993 // TODO: when HttpStateData sends all errors to ICAP,
994 // we should be able to move this at the end of setVirginReply().
996 Client::adaptOrFinalizeReply()
999 // TODO: merge with client side and return void to hide the on/off logic?
1000 // The callback can be called with a NULL service if adaptation is off.
1001 adaptationAccessCheckPending
= Adaptation::AccessCheck::Start(
1002 Adaptation::methodRespmod
, Adaptation::pointPreCache
,
1003 originalRequest().getRaw(), virginReply(), fwd
->al
, this);
1004 debugs(11,5, "adaptationAccessCheckPending=" << adaptationAccessCheckPending
);
1005 if (adaptationAccessCheckPending
)
1009 setFinalReply(virginReply());
1012 /// initializes bodyBytesRead stats if needed and applies delta
1014 Client::adjustBodyBytesRead(const int64_t delta
)
1016 int64_t &bodyBytesRead
= originalRequest()->hier
.bodyBytesRead
;
1018 // if we got here, do not log a dash even if we got nothing from the server
1019 if (bodyBytesRead
< 0)
1022 bodyBytesRead
+= delta
; // supports negative and zero deltas
1024 // check for overflows ("infinite" response?) and underflows (a bug)
1025 Must(bodyBytesRead
>= 0);
1031 using DeferredReadDialer
= NullaryMemFunT
<Client
>;
1032 AsyncCall::Pointer call
= asyncCall(11, 5, "Client::noteDelayAwareReadChance",
1033 DeferredReadDialer(this, &Client::noteDelayAwareReadChance
));
1034 entry
->mem().delayRead(call
);
1038 Client::addVirginReplyBody(const char *data
, ssize_t len
)
1040 adjustBodyBytesRead(len
);
1043 assert(!adaptationAccessCheckPending
); // or would need to buffer while waiting
1044 if (startedAdaptation
) {
1045 adaptVirginReplyBody(data
, len
);
1049 storeReplyBody(data
, len
);
1052 // writes virgin or adapted reply body to store
1054 Client::storeReplyBody(const char *data
, ssize_t len
)
1056 // write even if len is zero to push headers towards the client side
1057 entry
->write (StoreIOBuffer(len
, currentOffset
, (char*)data
));
1059 currentOffset
+= len
;
1063 Client::calcBufferSpaceToReserve(size_t space
, const size_t wantSpace
) const
1065 if (space
< wantSpace
) {
1066 const size_t maxSpace
= SBuf::maxSize
; // absolute best
1067 space
= min(wantSpace
, maxSpace
); // do not promise more than asked
1071 if (responseBodyBuffer
) {
1072 return 0; // Stop reading if already overflowed waiting for ICAP to catch up
1075 if (virginBodyDestination
!= nullptr) {
1077 * BodyPipe buffer has a finite size limit. We
1078 * should not read more data from the network than will fit
1079 * into the pipe buffer or we _lose_ what did not fit if
1080 * the response ends sooner that BodyPipe frees up space:
1081 * There is no code to keep pumping data into the pipe once
1082 * response ends and serverComplete() is called.
1084 const size_t adaptor_space
= virginBodyDestination
->buf().potentialSpaceSize();
1086 debugs(11,9, "Client may read up to min(" <<
1087 adaptor_space
<< ", " << space
<< ") bytes");
1089 if (adaptor_space
< space
)
1090 space
= adaptor_space
;
1098 Client::replyBodySpace(const MemBuf
&readBuf
, const size_t minSpace
) const
1100 size_t space
= readBuf
.spaceSize(); // available space w/o heroic measures
1101 if (space
< minSpace
) {
1102 const size_t maxSpace
= readBuf
.potentialSpaceSize(); // absolute best
1103 space
= min(minSpace
, maxSpace
); // do not promise more than asked
1107 if (responseBodyBuffer
) {
1108 return 0; // Stop reading if already overflowed waiting for ICAP to catch up
1111 if (virginBodyDestination
!= nullptr) {
1113 * BodyPipe buffer has a finite size limit. We
1114 * should not read more data from the network than will fit
1115 * into the pipe buffer or we _lose_ what did not fit if
1116 * the response ends sooner that BodyPipe frees up space:
1117 * There is no code to keep pumping data into the pipe once
1118 * response ends and serverComplete() is called.
1120 * If the pipe is totally full, don't register the read handler.
1121 * The BodyPipe will call our noteMoreBodySpaceAvailable() method
1122 * when it has free space again.
1124 size_t adaptation_space
=
1125 virginBodyDestination
->buf().potentialSpaceSize();
1127 debugs(11,9, "Client may read up to min(" <<
1128 adaptation_space
<< ", " << space
<< ") bytes");
1130 if (adaptation_space
< space
)
1131 space
= adaptation_space
;