2 * Copyright (C) 1996-2020 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 "err_detail_type.h"
18 #include "errorpage.h"
20 #include "HttpHdrContRange.h"
21 #include "HttpReply.h"
22 #include "HttpRequest.h"
23 #include "SquidConfig.h"
24 #include "SquidTime.h"
25 #include "StatCounters.h"
30 #include "adaptation/AccessCheck.h"
31 #include "adaptation/Answer.h"
32 #include "adaptation/Iterator.h"
33 #include "base/AsyncCall.h"
36 // implemented in client_side_reply.cc until sides have a common parent
37 void purgeEntriesByUrl(HttpRequest
* req
, const char *url
);
39 Client::Client(FwdState
*theFwdState
) :
45 entry
->lock("Client");
50 // paranoid: check that swanSong has been called
51 assert(!requestBodySource
);
53 assert(!virginBodyDestination
);
54 assert(!adaptedBodySource
);
57 entry
->unlock("Client");
59 HTTPMSGUNLOCK(theVirginReply
);
60 HTTPMSGUNLOCK(theFinalReply
);
62 if (responseBodyBuffer
!= NULL
) {
63 delete responseBodyBuffer
;
64 responseBodyBuffer
= NULL
;
71 // get rid of our piping obligations
72 if (requestBodySource
!= NULL
)
73 stopConsumingFrom(requestBodySource
);
79 if (!doneWithServer())
83 doneWithFwd
= "swanSong()";
84 fwd
->handleUnregisteredServerEnd();
87 BodyConsumer::swanSong();
89 Initiator::swanSong();
90 BodyProducer::swanSong();
93 // paranoid: check that swanSong has been called
94 // extra paranoid: yeah, I really mean it. they MUST pass here.
95 assert(!requestBodySource
);
97 assert(!virginBodyDestination
);
98 assert(!adaptedBodySource
);
103 Client::virginReply()
105 assert(theVirginReply
);
106 return theVirginReply
;
110 Client::virginReply() const
112 assert(theVirginReply
);
113 return theVirginReply
;
117 Client::setVirginReply(HttpReply
*rep
)
119 debugs(11,5, HERE
<< this << " setting virgin reply to " << rep
);
120 assert(!theVirginReply
);
122 theVirginReply
= rep
;
123 HTTPMSGLOCK(theVirginReply
);
125 fwd
->al
->reply
= theVirginReply
;
126 return theVirginReply
;
132 assert(theFinalReply
);
133 return theFinalReply
;
137 Client::setFinalReply(HttpReply
*rep
)
139 debugs(11,5, HERE
<< this << " setting final reply to " << rep
);
141 assert(!theFinalReply
);
144 HTTPMSGLOCK(theFinalReply
);
146 fwd
->al
->reply
= theFinalReply
;
148 // give entry the reply because haveParsedReplyHeaders() expects it there
149 entry
->replaceHttpReply(theFinalReply
, false); // but do not write yet
150 haveParsedReplyHeaders(); // update the entry/reply (e.g., set timestamps)
151 if (!EBIT_TEST(entry
->flags
, RELEASE_REQUEST
) && blockCaching())
153 entry
->startWriting(); // write the updated entry to store
155 return theFinalReply
;
158 // called when no more server communication is expected; may quit
160 Client::serverComplete()
162 debugs(11,5,HERE
<< "serverComplete " << this);
164 if (!doneWithServer()) {
166 assert(doneWithServer());
170 originalRequest()->hier
.stopPeerClock(true);
172 if (requestBodySource
!= NULL
)
173 stopConsumingFrom(requestBodySource
);
175 if (responseBodyBuffer
!= NULL
)
182 Client::serverComplete2()
184 debugs(11,5,HERE
<< "serverComplete2 " << this);
187 if (virginBodyDestination
!= NULL
)
188 stopProducingFor(virginBodyDestination
, true);
190 if (!doneWithAdaptation())
194 completeForwarding();
197 bool Client::doneAll() const
199 return doneWithServer() &&
201 doneWithAdaptation() &&
202 Adaptation::Initiator::doneAll() &&
203 BodyProducer::doneAll() &&
205 BodyConsumer::doneAll();
208 // FTP side overloads this to work around multiple calls to fwd->complete
210 Client::completeForwarding()
212 debugs(11,5, HERE
<< "completing forwarding for " << fwd
);
214 doneWithFwd
= "completeForwarding()";
218 // Register to receive request body
219 bool Client::startRequestBodyFlow()
221 HttpRequestPointer
r(originalRequest());
222 assert(r
->body_pipe
!= NULL
);
223 requestBodySource
= r
->body_pipe
;
224 if (requestBodySource
->setConsumerIfNotLate(this)) {
225 debugs(11,3, HERE
<< "expecting request body from " <<
226 requestBodySource
->status());
230 debugs(11,3, HERE
<< "aborting on partially consumed request body: " <<
231 requestBodySource
->status());
232 requestBodySource
= NULL
;
236 // Entry-dependent callbacks use this check to quit if the entry went bad
238 Client::abortOnBadEntry(const char *abortReason
)
240 if (entry
->isAccepting())
243 debugs(11,5, HERE
<< "entry is not Accepting!");
244 abortOnData(abortReason
);
248 // more request or adapted response body is available
250 Client::noteMoreBodyDataAvailable(BodyPipe::Pointer bp
)
253 if (adaptedBodySource
== bp
) {
254 handleMoreAdaptedBodyAvailable();
258 if (requestBodySource
== bp
)
259 handleMoreRequestBodyAvailable();
262 // the entire request or adapted response body was provided, successfully
264 Client::noteBodyProductionEnded(BodyPipe::Pointer bp
)
267 if (adaptedBodySource
== bp
) {
268 handleAdaptedBodyProductionEnded();
272 if (requestBodySource
== bp
)
273 handleRequestBodyProductionEnded();
276 // premature end of the request or adapted response body production
278 Client::noteBodyProducerAborted(BodyPipe::Pointer bp
)
281 if (adaptedBodySource
== bp
) {
282 handleAdaptedBodyProducerAborted();
286 if (requestBodySource
== bp
)
287 handleRequestBodyProducerAborted();
291 Client::abortOnData(const char *reason
)
297 // more origin request body data is available
299 Client::handleMoreRequestBodyAvailable()
302 sendMoreRequestBody();
304 debugs(9,3, HERE
<< "waiting for request body write to complete");
307 // there will be no more handleMoreRequestBodyAvailable calls
309 Client::handleRequestBodyProductionEnded()
311 receivedWholeRequestBody
= true;
313 doneSendingRequestBody();
315 debugs(9,3, HERE
<< "waiting for request body write to complete");
318 // called when we are done sending request body; kids extend this
320 Client::doneSendingRequestBody()
322 debugs(9,3, HERE
<< "done sending request body");
323 assert(requestBodySource
!= NULL
);
324 stopConsumingFrom(requestBodySource
);
329 // called when body producers aborts; kids extend this
331 Client::handleRequestBodyProducerAborted()
333 if (requestSender
!= NULL
)
334 debugs(9,3, HERE
<< "fyi: request body aborted while we were sending");
336 fwd
->dontRetry(true); // the problem is not with the server
337 stopConsumingFrom(requestBodySource
); // requestSender, if any, will notice
342 // called when we wrote request headers(!) or a part of the body
344 Client::sentRequestBody(const CommIoCbParams
&io
)
346 debugs(11, 5, "sentRequestBody: FD " << io
.fd
<< ": size " << io
.size
<< ": errflag " << io
.flag
<< ".");
347 debugs(32,3,HERE
<< "sentRequestBody called");
349 requestSender
= NULL
;
352 fd_bytes(io
.fd
, io
.size
, FD_WRITE
);
353 statCounter
.server
.all
.kbytes_out
+= io
.size
;
354 // kids should increment their counters
357 if (io
.flag
== Comm::ERR_CLOSING
)
360 if (!requestBodySource
) {
361 debugs(9,3, HERE
<< "detected while-we-were-sending abort");
362 return; // do nothing;
365 // both successful and failed writes affect response times
366 request
->hier
.notePeerWrite();
369 debugs(11, DBG_IMPORTANT
, "sentRequestBody error: FD " << io
.fd
<< ": " << xstrerr(io
.xerrno
));
371 err
= new ErrorState(ERR_WRITE_ERROR
, Http::scBadGateway
, fwd
->request
, fwd
->al
);
372 err
->xerrno
= io
.xerrno
;
374 abortOnData("I/O error while sending request body");
378 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
379 abortOnData("store entry aborted while sending request body");
383 if (!requestBodySource
->exhausted())
384 sendMoreRequestBody();
385 else if (receivedWholeRequestBody
)
386 doneSendingRequestBody();
388 debugs(9,3, HERE
<< "waiting for body production end or abort");
392 Client::sendMoreRequestBody()
394 assert(requestBodySource
!= NULL
);
395 assert(!requestSender
);
397 const Comm::ConnectionPointer conn
= dataConnection();
399 if (!Comm::IsConnOpen(conn
)) {
400 debugs(9,3, HERE
<< "cannot send request body to closing " << conn
);
401 return; // wait for the kid's close handler; TODO: assert(closer);
405 if (getMoreRequestBody(buf
) && buf
.contentSize() > 0) {
406 debugs(9,3, HERE
<< "will write " << buf
.contentSize() << " request body bytes");
407 typedef CommCbMemFunT
<Client
, CommIoCbParams
> Dialer
;
408 requestSender
= JobCallback(93,3, Dialer
, this, Client::sentRequestBody
);
409 Comm::Write(conn
, &buf
, requestSender
);
411 debugs(9,3, HERE
<< "will wait for more request body bytes or eof");
412 requestSender
= NULL
;
416 /// either fill buf with available [encoded] request body bytes or return false
418 Client::getMoreRequestBody(MemBuf
&buf
)
420 // default implementation does not encode request body content
421 Must(requestBodySource
!= NULL
);
422 return requestBodySource
->getMoreData(buf
);
425 // Compares hosts in urls, returns false if different, no sheme, or no host.
427 sameUrlHosts(const char *url1
, const char *url2
)
429 // XXX: Want AnyP::Uri::parse() here, but it uses static storage and copying
430 const char *host1
= strchr(url1
, ':');
431 const char *host2
= strchr(url2
, ':');
433 if (host1
&& host2
) {
434 // skip scheme slashes
438 } while (*host1
== '/' && *host2
== '/');
441 return false; // no host
443 // increment while the same until we reach the end of the URL/host
444 while (*host1
&& *host1
!= '/' && *host1
== *host2
) {
448 return *host1
== *host2
;
451 return false; // no URL scheme
454 // purges entries that match the value of a given HTTP [response] header
456 purgeEntriesByHeader(HttpRequest
*req
, const char *reqUrl
, Http::Message
*rep
, Http::HdrType hdr
)
458 const char *hdrUrl
, *absUrl
;
461 hdrUrl
= rep
->header
.getStr(hdr
);
462 if (hdrUrl
== NULL
) {
467 * If the URL is relative, make it absolute so we can find it.
468 * If it's absolute, make sure the host parts match to avoid DOS attacks
469 * as per RFC 2616 13.10.
471 if (urlIsRelative(hdrUrl
)) {
472 absUrl
= urlMakeAbsolute(req
, hdrUrl
);
473 if (absUrl
!= NULL
) {
476 } else if (!sameUrlHosts(reqUrl
, hdrUrl
)) {
480 purgeEntriesByUrl(req
, hdrUrl
);
482 if (absUrl
!= NULL
) {
487 // some HTTP methods should purge matching cache entries
489 Client::maybePurgeOthers()
491 // only some HTTP methods should purge matching cache entries
492 if (!request
->method
.purgesOthers())
495 // and probably only if the response was successful
496 if (theFinalReply
->sline
.status() >= 400)
499 // XXX: should we use originalRequest() here?
500 SBuf
tmp(request
->effectiveRequestUri());
501 const char *reqUrl
= tmp
.c_str();
502 debugs(88, 5, "maybe purging due to " << request
->method
<< ' ' << tmp
);
503 purgeEntriesByUrl(request
.getRaw(), reqUrl
);
504 purgeEntriesByHeader(request
.getRaw(), reqUrl
, theFinalReply
, Http::HdrType::LOCATION
);
505 purgeEntriesByHeader(request
.getRaw(), reqUrl
, theFinalReply
, Http::HdrType::CONTENT_LOCATION
);
508 /// called when we have final (possibly adapted) reply headers; kids extend
510 Client::haveParsedReplyHeaders()
515 // adaptation may overwrite old offset computed using the virgin response
516 const bool partial
= theFinalReply
->contentRange();
517 currentOffset
= partial
? theFinalReply
->contentRange()->spec
.offset
: 0;
520 /// whether to prevent caching of an otherwise cachable response
522 Client::blockCaching()
524 if (const Acl::Tree
*acl
= Config
.accessList
.storeMiss
) {
525 // This relatively expensive check is not in StoreEntry::checkCachable:
526 // That method lacks HttpRequest and may be called too many times.
527 ACLFilledChecklist
ch(acl
, originalRequest().getRaw());
528 ch
.reply
= const_cast<HttpReply
*>(&entry
->mem().freshestReply()); // ACLFilledChecklist API bug
529 HTTPMSGLOCK(ch
.reply
);
531 if (!ch
.fastCheck().allowed()) { // when in doubt, block
532 debugs(20, 3, "store_miss prohibits caching");
540 Client::originalRequest()
546 /// Initiate an asynchronous adaptation transaction which will call us back.
548 Client::startAdaptation(const Adaptation::ServiceGroupPointer
&group
, HttpRequest
*cause
)
550 debugs(11, 5, "Client::startAdaptation() called");
551 // check whether we should be sending a body as well
552 // start body pipe to feed ICAP transaction if needed
553 assert(!virginBodyDestination
);
554 HttpReply
*vrep
= virginReply();
555 assert(!vrep
->body_pipe
);
557 if (vrep
->expectingBody(cause
->method
, size
) && size
) {
558 virginBodyDestination
= new BodyPipe(this);
559 vrep
->body_pipe
= virginBodyDestination
;
560 debugs(93, 6, HERE
<< "will send virgin reply body to " <<
561 virginBodyDestination
<< "; size: " << size
);
563 virginBodyDestination
->setBodySize(size
);
566 adaptedHeadSource
= initiateAdaptation(
567 new Adaptation::Iterator(vrep
, cause
, fwd
->al
, group
));
568 startedAdaptation
= initiated(adaptedHeadSource
);
569 Must(startedAdaptation
);
572 // properly cleans up ICAP-related state
573 // may be called multiple times
574 void Client::cleanAdaptation()
576 debugs(11,5, HERE
<< "cleaning ICAP; ACL: " << adaptationAccessCheckPending
);
578 if (virginBodyDestination
!= NULL
)
579 stopProducingFor(virginBodyDestination
, false);
581 announceInitiatorAbort(adaptedHeadSource
);
583 if (adaptedBodySource
!= NULL
)
584 stopConsumingFrom(adaptedBodySource
);
586 if (!adaptationAccessCheckPending
) // we cannot cancel a pending callback
587 assert(doneWithAdaptation()); // make sure the two methods are in sync
591 Client::doneWithAdaptation() const
593 return !adaptationAccessCheckPending
&&
594 !virginBodyDestination
&& !adaptedHeadSource
&& !adaptedBodySource
;
597 // sends virgin reply body to ICAP, buffering excesses if needed
599 Client::adaptVirginReplyBody(const char *data
, ssize_t len
)
601 assert(startedAdaptation
);
603 if (!virginBodyDestination
) {
604 debugs(11,3, HERE
<< "ICAP does not want more virgin body");
608 // grow overflow area if already overflowed
609 if (responseBodyBuffer
) {
610 responseBodyBuffer
->append(data
, len
);
611 data
= responseBodyBuffer
->content();
612 len
= responseBodyBuffer
->contentSize();
615 const ssize_t putSize
= virginBodyDestination
->putMoreData(data
, len
);
619 // if we had overflow area, shrink it as necessary
620 if (responseBodyBuffer
) {
621 if (putSize
== responseBodyBuffer
->contentSize()) {
622 delete responseBodyBuffer
;
623 responseBodyBuffer
= NULL
;
625 responseBodyBuffer
->consume(putSize
);
630 // if we did not have an overflow area, create it as needed
632 assert(!responseBodyBuffer
);
633 responseBodyBuffer
= new MemBuf
;
634 responseBodyBuffer
->init(4096, SQUID_TCP_SO_RCVBUF
* 10);
635 responseBodyBuffer
->append(data
, len
);
639 // can supply more virgin response body data
641 Client::noteMoreBodySpaceAvailable(BodyPipe::Pointer
)
643 if (responseBodyBuffer
) {
644 addVirginReplyBody(NULL
, 0); // kick the buffered fragment alive again
645 if (completed
&& !responseBodyBuffer
) {
650 maybeReadVirginBody();
653 // the consumer of our virgin response body aborted
655 Client::noteBodyConsumerAborted(BodyPipe::Pointer
)
657 stopProducingFor(virginBodyDestination
, false);
659 // do not force closeServer here in case we need to bypass AdaptationQueryAbort
661 if (doneWithAdaptation()) // we may still be receiving adapted response
662 handleAdaptationCompleted();
665 // received adapted response headers (body may follow)
667 Client::noteAdaptationAnswer(const Adaptation::Answer
&answer
)
669 clearAdaptation(adaptedHeadSource
); // we do not expect more messages
671 switch (answer
.kind
) {
672 case Adaptation::Answer::akForward
:
673 handleAdaptedHeader(const_cast<Http::Message
*>(answer
.message
.getRaw()));
676 case Adaptation::Answer::akBlock
:
677 handleAdaptationBlocked(answer
);
680 case Adaptation::Answer::akError
:
681 handleAdaptationAborted(!answer
.final
);
687 Client::handleAdaptedHeader(Http::Message
*msg
)
689 if (abortOnBadEntry("entry went bad while waiting for adapted headers")) {
690 // If the adapted response has a body, the ICAP side needs to know
691 // that nobody will consume that body. We will be destroyed upon
692 // return. Tell the ICAP side that it is on its own.
693 HttpReply
*rep
= dynamic_cast<HttpReply
*>(msg
);
695 if (rep
->body_pipe
!= NULL
)
696 rep
->body_pipe
->expectNoConsumption();
701 HttpReply
*rep
= dynamic_cast<HttpReply
*>(msg
);
703 debugs(11,5, HERE
<< this << " setting adapted reply to " << rep
);
706 assert(!adaptedBodySource
);
707 if (rep
->body_pipe
!= NULL
) {
708 // subscribe to receive adapted body
709 adaptedBodySource
= rep
->body_pipe
;
710 // assume that ICAP does not auto-consume on failures
711 const bool result
= adaptedBodySource
->setConsumerIfNotLate(this);
715 if (doneWithAdaptation()) // we may still be sending virgin response
716 handleAdaptationCompleted();
721 Client::resumeBodyStorage()
723 if (abortOnBadEntry("store entry aborted while kick producer callback"))
726 if (!adaptedBodySource
)
729 handleMoreAdaptedBodyAvailable();
731 if (adaptedBodySource
!= NULL
&& adaptedBodySource
->exhausted())
732 endAdaptedBodyConsumption();
735 // more adapted response body is available
737 Client::handleMoreAdaptedBodyAvailable()
739 if (abortOnBadEntry("entry refuses adapted body"))
744 size_t contentSize
= adaptedBodySource
->buf().contentSize();
747 return; // XXX: bytesWanted asserts on zero-size ranges
749 const size_t spaceAvailable
= entry
->bytesWanted(Range
<size_t>(0, contentSize
), true);
751 if (spaceAvailable
< contentSize
) {
752 // No or partial body data consuming
753 typedef NullaryMemFunT
<Client
> Dialer
;
754 AsyncCall::Pointer call
= asyncCall(93, 5, "Client::resumeBodyStorage",
755 Dialer(this, &Client::resumeBodyStorage
));
756 entry
->deferProducer(call
);
759 if (!spaceAvailable
) {
760 debugs(11, 5, HERE
<< "NOT storing " << contentSize
<< " bytes of adapted " <<
761 "response body at offset " << adaptedBodySource
->consumedSize());
765 if (spaceAvailable
< contentSize
) {
766 debugs(11, 5, HERE
<< "postponing storage of " <<
767 (contentSize
- spaceAvailable
) << " body bytes");
768 contentSize
= spaceAvailable
;
771 debugs(11,5, HERE
<< "storing " << contentSize
<< " bytes of adapted " <<
772 "response body at offset " << adaptedBodySource
->consumedSize());
774 BodyPipeCheckout
bpc(*adaptedBodySource
);
775 const StoreIOBuffer
ioBuf(&bpc
.buf
, currentOffset
, contentSize
);
776 currentOffset
+= ioBuf
.length
;
778 bpc
.buf
.consume(contentSize
);
782 // the entire adapted response body was produced, successfully
784 Client::handleAdaptedBodyProductionEnded()
786 if (abortOnBadEntry("entry went bad while waiting for adapted body eof"))
789 // end consumption if we consumed everything
790 if (adaptedBodySource
!= NULL
&& adaptedBodySource
->exhausted())
791 endAdaptedBodyConsumption();
792 // else resumeBodyStorage() will eventually consume the rest
796 Client::endAdaptedBodyConsumption()
798 stopConsumingFrom(adaptedBodySource
);
799 handleAdaptationCompleted();
802 // premature end of the adapted response body
803 void Client::handleAdaptedBodyProducerAborted()
805 if (abortOnBadEntry("entry went bad while waiting for the now-aborted adapted body"))
808 Must(adaptedBodySource
!= nullptr);
809 if (!adaptedBodySource
->exhausted()) {
810 debugs(11,5, "waiting to consume the remainder of the aborted adapted body");
811 return; // resumeBodyStorage() should eventually consume the rest
814 stopConsumingFrom(adaptedBodySource
);
816 if (handledEarlyAdaptationAbort())
819 entry
->lengthWentBad("body adaptation aborted");
820 handleAdaptationCompleted(); // the user should get a truncated response
823 // common part of noteAdaptationAnswer and handleAdaptedBodyProductionEnded
825 Client::handleAdaptationCompleted()
827 debugs(11,5, HERE
<< "handleAdaptationCompleted");
830 // We stop reading origin response because we have no place to put it(*) and
831 // cannot use it. If some origin servers do not like that or if we want to
832 // reuse more pconns, we can add code to discard unneeded origin responses.
833 // (*) TODO: Is it possible that the adaptation xaction is still running?
834 if (mayReadVirginReplyBody()) {
835 debugs(11,3, HERE
<< "closing origin conn due to ICAP completion");
839 completeForwarding();
842 // common part of noteAdaptation*Aborted and noteBodyConsumerAborted methods
844 Client::handleAdaptationAborted(bool bypassable
)
846 debugs(11,5, HERE
<< "handleAdaptationAborted; bypassable: " << bypassable
<<
847 ", entry empty: " << entry
->isEmpty());
849 if (abortOnBadEntry("entry went bad while ICAP aborted"))
852 // TODO: bypass if possible
853 if (!handledEarlyAdaptationAbort())
854 abortAll("adaptation failure with a filled entry");
857 /// If the store entry is still empty, fully handles adaptation abort, returning
858 /// true. Otherwise just updates the request error detail and returns false.
860 Client::handledEarlyAdaptationAbort()
862 if (entry
->isEmpty()) {
863 debugs(11,8, "adaptation failure with an empty entry: " << *entry
);
864 const auto err
= new ErrorState(ERR_ICAP_FAILURE
, Http::scInternalServerError
, request
.getRaw(), fwd
->al
);
865 err
->detailError(ERR_DETAIL_ICAP_RESPMOD_EARLY
);
867 fwd
->dontRetry(true);
868 abortAll("adaptation failure with an empty entry");
869 return true; // handled
872 if (request
) // update logged info directly
873 request
->detailError(ERR_ICAP_FAILURE
, ERR_DETAIL_ICAP_RESPMOD_LATE
);
875 return false; // the caller must handle
878 // adaptation service wants us to deny HTTP client access to this response
880 Client::handleAdaptationBlocked(const Adaptation::Answer
&answer
)
882 debugs(11,5, HERE
<< answer
.ruleId
);
884 if (abortOnBadEntry("entry went bad while ICAP aborted"))
887 if (!entry
->isEmpty()) { // too late to block (should not really happen)
889 request
->detailError(ERR_ICAP_FAILURE
, ERR_DETAIL_RESPMOD_BLOCK_LATE
);
890 abortAll("late adaptation block");
894 debugs(11,7, HERE
<< "creating adaptation block response");
897 aclGetDenyInfoPage(&Config
.denyInfoList
, answer
.ruleId
.termedBuf(), 1);
898 if (page_id
== ERR_NONE
)
899 page_id
= ERR_ACCESS_DENIED
;
901 const auto err
= new ErrorState(page_id
, Http::scForbidden
, request
.getRaw(), fwd
->al
);
902 err
->detailError(ERR_DETAIL_RESPMOD_BLOCK_EARLY
);
904 fwd
->dontRetry(true);
906 abortOnData("timely adaptation block");
910 Client::noteAdaptationAclCheckDone(Adaptation::ServiceGroupPointer group
)
912 adaptationAccessCheckPending
= false;
914 if (abortOnBadEntry("entry went bad while waiting for ICAP ACL check"))
917 // TODO: Should non-ICAP and ICAP REPMOD pre-cache paths check this?
918 // That check now only happens on REQMOD pre-cache and REPMOD post-cache, in processReplyAccess().
919 if (virginReply()->expectedBodyTooLarge(*request
)) {
920 sendBodyIsTooLargeError();
923 // TODO: Should we check receivedBodyTooLarge as well?
926 debugs(11,3, HERE
<< "no adapation needed");
927 setFinalReply(virginReply());
932 startAdaptation(group
, originalRequest().getRaw());
938 Client::sendBodyIsTooLargeError()
940 const auto err
= new ErrorState(ERR_TOO_BIG
, Http::scForbidden
, request
.getRaw(), fwd
->al
);
942 fwd
->dontRetry(true);
943 abortOnData("Virgin body too large.");
946 // TODO: when HttpStateData sends all errors to ICAP,
947 // we should be able to move this at the end of setVirginReply().
949 Client::adaptOrFinalizeReply()
952 // TODO: merge with client side and return void to hide the on/off logic?
953 // The callback can be called with a NULL service if adaptation is off.
954 adaptationAccessCheckPending
= Adaptation::AccessCheck::Start(
955 Adaptation::methodRespmod
, Adaptation::pointPreCache
,
956 originalRequest().getRaw(), virginReply(), fwd
->al
, this);
957 debugs(11,5, HERE
<< "adaptationAccessCheckPending=" << adaptationAccessCheckPending
);
958 if (adaptationAccessCheckPending
)
962 setFinalReply(virginReply());
965 /// initializes bodyBytesRead stats if needed and applies delta
967 Client::adjustBodyBytesRead(const int64_t delta
)
969 int64_t &bodyBytesRead
= originalRequest()->hier
.bodyBytesRead
;
971 // if we got here, do not log a dash even if we got nothing from the server
972 if (bodyBytesRead
< 0)
975 bodyBytesRead
+= delta
; // supports negative and zero deltas
977 // check for overflows ("infinite" response?) and underflows (a bug)
978 Must(bodyBytesRead
>= 0);
982 Client::addVirginReplyBody(const char *data
, ssize_t len
)
984 adjustBodyBytesRead(len
);
987 assert(!adaptationAccessCheckPending
); // or would need to buffer while waiting
988 if (startedAdaptation
) {
989 adaptVirginReplyBody(data
, len
);
993 storeReplyBody(data
, len
);
996 // writes virgin or adapted reply body to store
998 Client::storeReplyBody(const char *data
, ssize_t len
)
1000 // write even if len is zero to push headers towards the client side
1001 entry
->write (StoreIOBuffer(len
, currentOffset
, (char*)data
));
1003 currentOffset
+= len
;
1007 Client::calcBufferSpaceToReserve(size_t space
, const size_t wantSpace
) const
1009 if (space
< wantSpace
) {
1010 const size_t maxSpace
= SBuf::maxSize
; // absolute best
1011 space
= min(wantSpace
, maxSpace
); // do not promise more than asked
1015 if (responseBodyBuffer
) {
1016 return 0; // Stop reading if already overflowed waiting for ICAP to catch up
1019 if (virginBodyDestination
!= NULL
) {
1021 * BodyPipe buffer has a finite size limit. We
1022 * should not read more data from the network than will fit
1023 * into the pipe buffer or we _lose_ what did not fit if
1024 * the response ends sooner that BodyPipe frees up space:
1025 * There is no code to keep pumping data into the pipe once
1026 * response ends and serverComplete() is called.
1028 const size_t adaptor_space
= virginBodyDestination
->buf().potentialSpaceSize();
1030 debugs(11,9, "Client may read up to min(" <<
1031 adaptor_space
<< ", " << space
<< ") bytes");
1033 if (adaptor_space
< space
)
1034 space
= adaptor_space
;
1042 Client::replyBodySpace(const MemBuf
&readBuf
, const size_t minSpace
) const
1044 size_t space
= readBuf
.spaceSize(); // available space w/o heroic measures
1045 if (space
< minSpace
) {
1046 const size_t maxSpace
= readBuf
.potentialSpaceSize(); // absolute best
1047 space
= min(minSpace
, maxSpace
); // do not promise more than asked
1051 if (responseBodyBuffer
) {
1052 return 0; // Stop reading if already overflowed waiting for ICAP to catch up
1055 if (virginBodyDestination
!= NULL
) {
1057 * BodyPipe buffer has a finite size limit. We
1058 * should not read more data from the network than will fit
1059 * into the pipe buffer or we _lose_ what did not fit if
1060 * the response ends sooner that BodyPipe frees up space:
1061 * There is no code to keep pumping data into the pipe once
1062 * response ends and serverComplete() is called.
1064 * If the pipe is totally full, don't register the read handler.
1065 * The BodyPipe will call our noteMoreBodySpaceAvailable() method
1066 * when it has free space again.
1068 size_t adaptation_space
=
1069 virginBodyDestination
->buf().potentialSpaceSize();
1071 debugs(11,9, "Client may read up to min(" <<
1072 adaptation_space
<< ", " << space
<< ") bytes");
1074 if (adaptation_space
< space
)
1075 space
= adaptation_space
;