# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: rousskov@measurement-factory.com-20110308235622-\ # l6xrjqvfk0d8pvts # target_branch: http://www.squid-cache.org/bzr/squid3/trunk # testament_sha1: ecc3c3a08553a8970367d44f9f80b948699f220a # timestamp: 2011-03-08 16:59:40 -0700 # base_revision_id: rousskov@measurement-factory.com-20110221043106-\ # sfoy6edu7x3k81sy # # Begin patch === modified file 'configure.ac' --- configure.ac 2011-02-20 12:35:59 +0000 +++ configure.ac 2011-03-08 23:56:22 +0000 @@ -3,7 +3,7 @@ dnl dnl dnl -AC_INIT([Squid Web Proxy],[3.HEAD-BZR],[http://www.squid-cache.org/bugs/],[squid]) +AC_INIT([Squid Web Proxy],[3.2-ecap-BZR],[http://www.squid-cache.org/bugs/],[squid]) AC_PREREQ(2.61) AC_CONFIG_HEADERS([include/autoconf.h]) AC_CONFIG_AUX_DIR(cfgaux) @@ -901,8 +901,10 @@ ] ) +dnl Necessary if the first PKG_CHECK_MODULES call is conditional +PKG_PROG_PKG_CONFIG + dnl Perform configuration consistency checks for eCAP -ECAPLIB="" if test "x$squid_opt_use_ecap" = "xyes"; then dnl eCAP support requires loadable modules, which are enabled by default @@ -911,18 +913,18 @@ AC_MSG_ERROR([eCAP support requires loadable modules. Please do not use --disable-loadable-modules with --enable-ecap.]); fi - dnl eCAP support requires libecap - AC_CHECK_LIB([ecap], [main], - [ECAPLIB="-lecap"], - [AC_MSG_FAILURE([eCAP support requires libecap library, but no usable library was found])] - ) + dnl eCAP support requires libecap. + dnl This Squid supports libecap v0.2.x. + dnl Use EXT prefix so that make and libtool messages distinguish between + dnl external libecap (that we check for here) and our own convenience lib. + PKG_CHECK_MODULES(EXTLIBECAP, [libecap > 0.2 libecap < 0.3]) fi AM_CONDITIONAL(USE_ECAP, test "x$squid_opt_use_ecap" = "xyes") if test "x$squid_opt_use_ecap" = "xyes"; then AC_DEFINE(USE_ECAP,1,[Enable eCAP support]) - ECAP_LIBS="ecap/libecap.la $ECAP_LIBS" + ECAP_LIBS="ecap/libxecap.la" squid_opt_use_adaptation=yes else AC_DEFINE(USE_ECAP,0,[Disable eCAP support]) @@ -930,8 +932,6 @@ fi dnl convenience library AC_SUBST(ECAP_LIBS) -dnl -lecap if needed -AC_SUBST(ECAPLIB) dnl enable adaptation if requested by specific adaptation mechanisms === modified file 'src/AccessLogEntry.h' --- src/AccessLogEntry.h 2010-08-08 00:12:41 +0000 +++ src/AccessLogEntry.h 2011-02-18 19:39:05 +0000 @@ -155,8 +155,8 @@ Headers() : request(NULL), adapted_request(NULL), -#if ICAP_CLIENT - icap(NULL), +#if USE_ADAPTATION + adapt_last(NULL), #endif reply(NULL) {} @@ -165,8 +165,9 @@ char *adapted_request; //< HTTP request headers after adaptation and redirection -#if ICAP_CLIENT - char * icap; ///< last matching ICAP response header. +#if USE_ADAPTATION + + char *adapt_last; ///< last ICAP response header or eCAP meta received #endif char *reply; } headers; === modified file 'src/BodyPipe.cc' --- src/BodyPipe.cc 2010-09-13 00:16:57 +0000 +++ src/BodyPipe.cc 2010-12-19 03:51:48 +0000 @@ -10,24 +10,25 @@ // data from a BodyPipe class BodySink: public BodyConsumer { - bool done; public: - BodySink():AsyncJob("BodySink"), done(false) {} - virtual ~BodySink() {} + BodySink(const BodyPipe::Pointer &bp): AsyncJob("BodySink"), body_pipe(bp) {} + virtual ~BodySink() { assert(!body_pipe); } virtual void noteMoreBodyDataAvailable(BodyPipe::Pointer bp) { size_t contentSize = bp->buf().contentSize(); bp->consume(contentSize); } virtual void noteBodyProductionEnded(BodyPipe::Pointer bp) { - stopConsumingFrom(bp); - done = true; + stopConsumingFrom(body_pipe); } virtual void noteBodyProducerAborted(BodyPipe::Pointer bp) { - stopConsumingFrom(bp); - done = true; + stopConsumingFrom(body_pipe); } - bool doneAll() const {return done && AsyncJob::doneAll();} + bool doneAll() const {return !body_pipe && AsyncJob::doneAll();} + +private: + BodyPipe::Pointer body_pipe; ///< the pipe we are consuming from + CBDATA_CLASS2(BodySink); }; @@ -318,7 +319,7 @@ { Must(mustAutoConsume); Must(!theConsumer); - theConsumer = new BodySink; + theConsumer = new BodySink(this); debugs(91,7, HERE << "starting auto consumption" << status()); scheduleBodyDataNotification(); } === modified file 'src/HttpMsg.h' --- src/HttpMsg.h 2010-09-11 23:56:07 +0000 +++ src/HttpMsg.h 2010-12-15 17:04:09 +0000 @@ -220,8 +220,7 @@ const Msg &operator *() const { return *msg; } Msg *operator ->() { return msg; } const Msg *operator ->() const { return msg; } - operator Msg *() { return msg; } - operator const Msg *() const { return msg; } + operator Msg *() const { return msg; } // add more as needed /// public access for HttpMsgPointerT copying and assignment; avoid === modified file 'src/Server.cc' --- src/Server.cc 2010-11-27 01:46:22 +0000 +++ src/Server.cc 2011-03-08 23:56:22 +0000 @@ -33,6 +33,7 @@ */ #include "squid.h" +#include "acl/Gadgets.h" #include "base/TextException.h" #include "comm/Write.h" #include "Server.h" @@ -660,10 +661,28 @@ // received adapted response headers (body may follow) void -ServerStateData::noteAdaptationAnswer(HttpMsg *msg) +ServerStateData::noteAdaptationAnswer(const Adaptation::Answer &answer) { clearAdaptation(adaptedHeadSource); // we do not expect more messages + switch (answer.kind) { + case Adaptation::Answer::akForward: + handleAdaptedHeader(answer.message); + break; + + case Adaptation::Answer::akBlock: + handleAdaptationBlocked(answer); + break; + + case Adaptation::Answer::akError: + handleAdaptationAborted(!answer.final); + break; + } +} + +void +ServerStateData::handleAdaptedHeader(HttpMsg *msg) +{ if (abortOnBadEntry("entry went bad while waiting for adapted headers")) return; @@ -685,14 +704,6 @@ } } -// will not receive adapted response headers (and, hence, body) -void -ServerStateData::noteAdaptationQueryAbort(bool final) -{ - clearAdaptation(adaptedHeadSource); - handleAdaptationAborted(!final); -} - // more adapted response body is available void ServerStateData::handleMoreAdaptedBodyAvailable() @@ -778,6 +789,37 @@ abortTransaction("ICAP failure"); } +// adaptation service wants us to deny HTTP client access to this response +void +ServerStateData::handleAdaptationBlocked(const Adaptation::Answer &answer) +{ + debugs(11,5, HERE << answer.ruleId); + + if (abortOnBadEntry("entry went bad while ICAP aborted")) + return; + + if (!entry->isEmpty()) { // too late to block (should not really happen) + if (request) + request->detailError(ERR_ICAP_FAILURE, ERR_DETAIL_RESPMOD_BLOCK_LATE); + abortTransaction("late adaptation block"); + return; + } + + debugs(11,7, HERE << "creating adaptation block response"); + + err_type page_id = + aclGetDenyInfoPage(&Config.denyInfoList, answer.ruleId.termedBuf(), 1); + if (page_id == ERR_NONE) + page_id = ERR_ACCESS_DENIED; + + ErrorState *err = errorCon(page_id, HTTP_FORBIDDEN, request); + err->xerrno = ERR_DETAIL_RESPMOD_BLOCK_EARLY; + fwd->fail(err); + fwd->dontRetry(true); + + abortTransaction("timely adaptation block"); +} + void ServerStateData::adaptationAclCheckDone(Adaptation::ServiceGroupPointer group) { === modified file 'src/Server.h' --- src/Server.h 2010-11-27 01:46:22 +0000 +++ src/Server.h 2010-12-15 17:52:35 +0000 @@ -90,8 +90,7 @@ static void adaptationAclCheckDoneWrapper(Adaptation::ServiceGroupPointer group, void *data); // ICAPInitiator: start an ICAP transaction and receive adapted headers. - virtual void noteAdaptationAnswer(HttpMsg *message); - virtual void noteAdaptationQueryAbort(bool final); + virtual void noteAdaptationAnswer(const Adaptation::Answer &answer); // BodyProducer: provide virgin response body to ICAP. virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer ); @@ -154,7 +153,9 @@ void handleAdaptedBodyProductionEnded(); void handleAdaptedBodyProducerAborted(); + void handleAdaptedHeader(HttpMsg *msg); void handleAdaptationCompleted(); + void handleAdaptationBlocked(const Adaptation::Answer &answer); void handleAdaptationAborted(bool bypassable = false); #endif === modified file 'src/adaptation/Config.cc' --- src/adaptation/Config.cc 2010-05-27 00:51:44 +0000 +++ src/adaptation/Config.cc 2011-02-17 19:27:54 +0000 @@ -47,11 +47,21 @@ bool Adaptation::Config::Enabled = false; char *Adaptation::Config::masterx_shared_name = NULL; int Adaptation::Config::service_iteration_limit = 16; +int Adaptation::Config::send_client_ip = false; +int Adaptation::Config::send_username = false; +int Adaptation::Config::use_indirect_client = true; + + +Adaptation::ServiceConfig* +Adaptation::Config::newServiceConfig() const +{ + return new ServiceConfig(); +} void Adaptation::Config::parseService() { - ServiceConfig *cfg = new ServiceConfig; + ServiceConfigPointer cfg = newServiceConfig(); if (!cfg->parse()) { fatalf("%s:%d: malformed adaptation service configuration", cfg_filename, config_lineno); @@ -67,10 +77,7 @@ DetachServices(); - while (!serviceConfigs.empty()) { - delete serviceConfigs.back(); - serviceConfigs.pop_back(); - } + serviceConfigs.clean(); } void @@ -91,23 +98,28 @@ Adaptation::Config::finalize() { // create service reps from service configs - typedef Vector::const_iterator VISCI; - const Vector &configs = serviceConfigs; - debugs(93,3, HERE << "Found " << configs.size() << " service configs."); + int created = 0; + + typedef ServiceConfigs::const_iterator VISCI; + const ServiceConfigs &configs = serviceConfigs; for (VISCI i = configs.begin(); i != configs.end(); ++i) { - const ServiceConfig &cfg = **i; - if (FindService(cfg.key) != NULL) { + const ServiceConfigPointer cfg = *i; + if (FindService(cfg->key) != NULL) { debugs(93,0, "ERROR: Duplicate adaptation service name: " << - cfg.key); + cfg->key); continue; // TODO: make fatal } - ServicePointer s = createService(**i); - if (s != NULL) + ServicePointer s = createService(cfg); + if (s != NULL) { AllServices().push_back(s); + created++; + } } - debugs(93,3, HERE << "Created " << configs.size() << - " message adaptation services."); + debugs(93,3, HERE << "Created " << created << " adaptation services"); + + // services remember their configs; we do not have to + serviceConfigs.clean(); } // poor man for_each === modified file 'src/adaptation/Config.h' --- src/adaptation/Config.h 2010-04-05 10:29:50 +0000 +++ src/adaptation/Config.h 2011-03-08 23:56:22 +0000 @@ -32,17 +32,19 @@ // these are global squid.conf options, documented elsewhere static char *masterx_shared_name; // global TODO: do we need TheConfig? static int service_iteration_limit; + static int send_client_ip; + static int send_username; + static int use_indirect_client; + // Options below are accessed via Icap::TheConfig or Ecap::TheConfig // TODO: move ICAP-specific options to Icap::Config and add TheConfig int onoff; - int send_client_ip; - int send_client_username; int service_failure_limit; time_t oldest_service_failure; int service_revival_delay; - int icap_uses_indirect_client; - Vector serviceConfigs; + typedef Vector ServiceConfigs; + ServiceConfigs serviceConfigs; Config(); virtual ~Config(); @@ -54,11 +56,15 @@ virtual void finalize(); +protected: + /// creates service configuration object that will parse and keep cfg info + virtual ServiceConfig *newServiceConfig() const; + private: Config(const Config &); // unsupported Config &operator =(const Config &); // unsupported - virtual ServicePointer createService(const ServiceConfig &cfg) = 0; + virtual ServicePointer createService(const ServiceConfigPointer &cfg) = 0; static void ParseServiceGroup(ServiceGroupPointer group); static void FreeServiceGroups(void); === modified file 'src/adaptation/History.cc' --- src/adaptation/History.cc 2010-09-12 22:28:31 +0000 +++ src/adaptation/History.cc 2011-02-18 19:39:05 +0000 @@ -33,7 +33,10 @@ } -Adaptation::History::History(): theNextServices(TheNullServices) +Adaptation::History::History(): + lastMeta(hoReply), + allMeta(hoReply), + theNextServices(TheNullServices) { } @@ -136,3 +139,12 @@ theNextServices = TheNullServices; // prevents resetting the plan twice return true; } + +void Adaptation::History::recordMeta(const HttpHeader *lm) +{ + lastMeta.clean(); + lastMeta.update(lm, NULL); + + allMeta.update(lm, NULL); + allMeta.compact(); +} === modified file 'src/adaptation/History.h' --- src/adaptation/History.h 2009-08-23 09:30:49 +0000 +++ src/adaptation/History.h 2011-02-18 19:39:05 +0000 @@ -1,8 +1,9 @@ #ifndef SQUID_ADAPT_HISTORY_H #define SQUID_ADAPT_HISTORY_H +#include "Array.h" +#include "HttpHeader.h" #include "RefCount.h" -#include "Array.h" #include "SquidString.h" namespace Adaptation @@ -41,6 +42,15 @@ /// returns true, fills the value, and resets iff next services were set bool extractNextServices(String &value); + /// store the last meta header fields received from the adaptation service + void recordMeta(const HttpHeader *lm); + +public: + /// Last received meta header (REQMOD or RESPMOD, whichever comes last). + HttpHeader lastMeta; + /// All REQMOD and RESPMOD meta headers merged. Last field wins conflicts. + HttpHeader allMeta; + private: /// single Xaction stats (i.e., a historical record entry) class Entry === modified file 'src/adaptation/Initiate.cc' --- src/adaptation/Initiate.cc 2010-08-24 00:12:54 +0000 +++ src/adaptation/Initiate.cc 2010-12-15 17:52:35 +0000 @@ -8,29 +8,6 @@ #include "adaptation/Initiate.h" #include "base/AsyncJobCalls.h" -namespace Adaptation -{ - -// AdaptInitiator::noteAdaptionAnswer Dialer locks/unlocks the message in transit -// TODO: replace HTTPMSGLOCK with general RefCounting and delete this class -class AnswerDialer: public UnaryMemFunT -{ -public: - typedef UnaryMemFunT Parent; - - AnswerDialer(const Parent::JobPointer &job, Parent::Method meth, - HttpMsg *msg): Parent(job, meth, msg) { HTTPMSGLOCK(arg1); } - AnswerDialer(const AnswerDialer &d): Parent(d) { HTTPMSGLOCK(arg1); } - virtual ~AnswerDialer() { HTTPMSGUNLOCK(arg1); } - -private: - AnswerDialer &operator =(const AnswerDialer &); // not implemented -}; - -} // namespace Adaptation - - -/* Initiate */ Adaptation::Initiate::Initiate(const char *aTypeName): AsyncJob(aTypeName) { @@ -70,20 +47,18 @@ theInitiator.clear(); } -void Adaptation::Initiate::sendAnswer(HttpMsg *msg) +void Adaptation::Initiate::sendAnswer(const Answer &answer) { - assert(msg); + typedef UnaryMemFunT MyDialer; CallJob(93, 5, __FILE__, __LINE__, "Initiator::noteAdaptationAnswer", - AnswerDialer(theInitiator, &Initiator::noteAdaptationAnswer, msg)); + MyDialer(theInitiator, &Initiator::noteAdaptationAnswer, answer)); clearInitiator(); } void Adaptation::Initiate::tellQueryAborted(bool final) { - CallJobHere1(93, 5, theInitiator, - Initiator, noteAdaptationQueryAbort, final); - clearInitiator(); + sendAnswer(Answer::Error(final)); } const char *Adaptation::Initiate::status() const === modified file 'src/adaptation/Initiate.h' --- src/adaptation/Initiate.h 2010-08-23 23:15:26 +0000 +++ src/adaptation/Initiate.h 2010-12-15 17:52:35 +0000 @@ -5,8 +5,6 @@ #include "base/CbcPointer.h" #include "adaptation/forward.h" -class HttpMsg; - namespace Adaptation { @@ -34,7 +32,7 @@ virtual void noteInitiatorAborted() = 0; protected: - void sendAnswer(HttpMsg *msg); // send to the initiator + void sendAnswer(const Answer &answer); // send to the initiator void tellQueryAborted(bool final); // tell initiator void clearInitiator(); // used by noteInitiatorAborted; TODO: make private === modified file 'src/adaptation/Initiator.cc' --- src/adaptation/Initiator.cc 2010-08-23 23:15:26 +0000 +++ src/adaptation/Initiator.cc 2010-12-15 17:52:35 +0000 @@ -28,3 +28,46 @@ CallJobHere(93, 5, x, Initiate, noteInitiatorAborted); clearAdaptation(x); } + + +/* Adaptation::Answer */ + +// TODO: Move to src/adaptation/Answer.* + +Adaptation::Answer +Adaptation::Answer::Error(bool final) +{ + Answer answer(akError); + answer.final = final; + debugs(93, 4, HERE << "error: " << final); + return answer; +} + +Adaptation::Answer +Adaptation::Answer::Forward(HttpMsg *aMsg) +{ + Answer answer(akForward); + answer.message = aMsg; + debugs(93, 4, HERE << "forwarding: " << (void*)aMsg); + return answer; +} + + +Adaptation::Answer +Adaptation::Answer::Block(const String &aRule) +{ + Answer answer(akBlock); + answer.ruleId = aRule; + debugs(93, 4, HERE << "blocking rule: " << aRule); + return answer; +} + +std::ostream & +Adaptation::Answer::print(std::ostream &os) const +{ + return os << kind; // TODO: add more details +} + +Adaptation::Answer::Answer(Kind aKind): final(true), kind(aKind) +{ +} === modified file 'src/adaptation/Initiator.h' --- src/adaptation/Initiator.h 2010-08-23 23:15:26 +0000 +++ src/adaptation/Initiator.h 2010-12-15 17:52:35 +0000 @@ -4,6 +4,9 @@ #include "base/AsyncJob.h" #include "base/CbcPointer.h" #include "adaptation/forward.h" +#include "HttpMsg.h" + +#include /* * The ICAP Initiator is an ICAP vectoring point that initates ICAP @@ -14,23 +17,50 @@ * or aborting an ICAP transaction. */ -class HttpMsg; - namespace Adaptation { +/// summarizes adaptation service answer for the noteAdaptationAnswer() API +class Answer { +public: + /// helps interpret other members without a class hierarchy + typedef enum { + akForward, ///< forward the supplied adapted HTTP message + akBlock, ///< block or deny the master xaction; see authority + akError, ///< no adapted message will come; see bypassable + } Kind; + + static Answer Error(bool final); ///< create an akError answer + static Answer Forward(HttpMsg *aMsg); ///< create an akForward answer + static Answer Block(const String &aRule); ///< create an akBlock answer + + std::ostream &print(std::ostream &os) const; + +public: + HttpMsgPointerT message; ///< HTTP request or response to forward + String ruleId; ///< ACL (or similar rule) name that blocked forwarding + bool final; ///< whether the error, if any, cannot be bypassed + Kind kind; ///< the type of the answer + +private: + explicit Answer(Kind aKind); ///< use static creators instead +}; + +inline +std::ostream &operator <<(std::ostream &os, const Answer &answer) +{ + return answer.print(os); +} + class Initiator: virtual public AsyncJob { public: Initiator(): AsyncJob("Initiator") {} virtual ~Initiator() {} - // called when ICAP response headers are successfully interpreted - virtual void noteAdaptationAnswer(HttpMsg *message) = 0; - - // called when valid ICAP response headers are no longer expected - // the final parameter is set to disable bypass or retries - virtual void noteAdaptationQueryAbort(bool final) = 0; + /// called with the initial adaptation decision (adapt, block, error); + /// virgin and/or adapted body transmission may continue after this + virtual void noteAdaptationAnswer(const Answer &answer) = 0; protected: ///< starts freshly created initiate and returns a safe pointer to it === modified file 'src/adaptation/Iterator.cc' --- src/adaptation/Iterator.cc 2010-08-24 00:12:54 +0000 +++ src/adaptation/Iterator.cc 2010-12-15 17:52:35 +0000 @@ -51,7 +51,7 @@ Must(!theLauncher); if (thePlan.exhausted()) { // nothing more to do - sendAnswer(theMsg); + sendAnswer(Answer::Forward(theMsg)); Must(done()); return; } @@ -74,7 +74,26 @@ Must(!done()); } -void Adaptation::Iterator::noteAdaptationAnswer(HttpMsg *aMsg) +void +Adaptation::Iterator::noteAdaptationAnswer(const Answer &answer) +{ + switch (answer.kind) { + case Answer::akForward: + handleAdaptedHeader(answer.message); + break; + + case Answer::akBlock: + handleAdaptationBlock(answer); + break; + + case Answer::akError: + handleAdaptationError(answer.final); + break; + } +} + +void +Adaptation::Iterator::handleAdaptedHeader(HttpMsg *aMsg) { // set theCause if we switched to request satisfaction mode if (!theCause) { // probably sent a request message @@ -106,7 +125,16 @@ mustStop("initiator gone"); } -void Adaptation::Iterator::noteAdaptationQueryAbort(bool final) +void Adaptation::Iterator::handleAdaptationBlock(const Answer &answer) +{ + debugs(93,5, HERE << "blocked by " << answer); + clearAdaptation(theLauncher); + updatePlan(false); + sendAnswer(answer); + mustStop("blocked"); +} + +void Adaptation::Iterator::handleAdaptationError(bool final) { debugs(93,5, HERE << "final: " << final << " plan: " << thePlan); clearAdaptation(theLauncher); @@ -130,7 +158,7 @@ if (canIgnore && srcIntact && adapted) { debugs(85,3, HERE << "responding with older adapted msg"); - sendAnswer(theMsg); + sendAnswer(Answer::Forward(theMsg)); mustStop("sent older adapted msg"); return; } === modified file 'src/adaptation/Iterator.h' --- src/adaptation/Iterator.h 2010-08-23 23:15:26 +0000 +++ src/adaptation/Iterator.h 2010-12-15 17:52:35 +0000 @@ -29,8 +29,7 @@ void noteInitiatorAborted(); // Adaptation::Initiator: asynchronous communication with the current launcher - virtual void noteAdaptationAnswer(HttpMsg *message); - virtual void noteAdaptationQueryAbort(bool final); + virtual void noteAdaptationAnswer(const Answer &answer); protected: // Adaptation::Initiate API implementation @@ -47,6 +46,10 @@ /// creates service filter for the current step ServiceFilter filter() const; + void handleAdaptedHeader(HttpMsg *msg); + void handleAdaptationBlock(const Answer &answer); + void handleAdaptationError(bool final); + ServiceGroupPointer theGroup; ///< the service group we are iterating ServicePlan thePlan; ///< which services to use and in what order HttpMsg *theMsg; ///< the message being adapted (virgin for each step) === modified file 'src/adaptation/Makefile.am' --- src/adaptation/Makefile.am 2010-08-26 01:12:07 +0000 +++ src/adaptation/Makefile.am 2011-03-08 23:56:22 +0000 @@ -45,6 +45,6 @@ History.h # add libraries for specific adaptation schemes -libadaptation_la_LIBADD = $(ECAP_LIBS) $(ECAPLIB) $(ICAP_LIBS) +libadaptation_la_LIBADD = $(EXTLIBECAP_LIBS) $(ECAP_LIBS) $(ICAP_LIBS) libadaptation_la_DEPENDENCIES = $(ECAP_LIBS) $(ICAP_LIBS) === modified file 'src/adaptation/Service.cc' --- src/adaptation/Service.cc 2010-05-27 00:51:44 +0000 +++ src/adaptation/Service.cc 2011-03-08 23:56:22 +0000 @@ -7,9 +7,10 @@ #include "adaptation/ServiceFilter.h" #include "adaptation/Service.h" -Adaptation::Service::Service(const ServiceConfig &aConfig): theConfig(aConfig) +Adaptation::Service::Service(const ServiceConfigPointer &aConfig): theConfig(aConfig) { - debugs(93,3, HERE << "creating adaptation service " << theConfig.key); + Must(theConfig != NULL); + debugs(93,3, HERE << "creating adaptation service " << cfg().key); } Adaptation::Service::~Service() === modified file 'src/adaptation/Service.h' --- src/adaptation/Service.h 2010-08-23 23:15:26 +0000 +++ src/adaptation/Service.h 2011-03-08 23:56:22 +0000 @@ -24,7 +24,7 @@ typedef String Id; public: - Service(const ServiceConfig &aConfig); + explicit Service(const ServiceConfigPointer &aConfig); virtual ~Service(); virtual bool probed() const = 0; // see comments above @@ -41,7 +41,7 @@ // called by transactions to report service failure virtual void noteFailure() = 0; - const ServiceConfig &cfg() const { return theConfig; } + const ServiceConfig &cfg() const { return *theConfig; } virtual void finalize(); // called after creation @@ -52,10 +52,10 @@ virtual bool detached() const = 0; protected: - ServiceConfig &writeableCfg() { return theConfig; } + ServiceConfig &writeableCfg() { return *theConfig; } private: - ServiceConfig theConfig; + ServiceConfigPointer theConfig; }; typedef Service::Pointer ServicePointer; === modified file 'src/adaptation/ServiceConfig.cc' --- src/adaptation/ServiceConfig.cc 2011-01-14 17:45:23 +0000 +++ src/adaptation/ServiceConfig.cc 2011-03-08 23:56:22 +0000 @@ -69,6 +69,7 @@ // handle optional service name=value parameters const char *lastOption = NULL; + bool grokkedUri = false; while (char *option = strtok(NULL, w_space)) { if (strcmp(option, "0") == 0) { // backward compatibility bypass = false; @@ -94,20 +95,21 @@ grokked = grokBool(bypass, name, value); else if (strcmp(name, "routing") == 0) grokked = grokBool(routing, name, value); + else if (strcmp(name, "uri") == 0) + grokked = grokkedUri = grokUri(value); else if (strcmp(name, "ipv6") == 0) { grokked = grokBool(ipv6, name, value); if (grokked && ipv6 && !Ip::EnableIpv6) debugs(3, DBG_IMPORTANT, "WARNING: IPv6 is disabled. ICAP service option ignored."); - } else { - debugs(3, 0, cfg_filename << ':' << config_lineno << ": " << - "unknown adaptation service option: " << name << '=' << value); - } + } else + grokked = grokExtension(name, value); + if (!grokked) return false; } // what is left must be the service URI - if (!grokUri(lastOption)) + if (!grokkedUri && !grokUri(lastOption)) return false; // there should be nothing else left @@ -243,3 +245,13 @@ return true; } + +bool +Adaptation::ServiceConfig::grokExtension(const char *name, const char *value) +{ + // we do not accept extensions by default + debugs(3, DBG_CRITICAL, cfg_filename << ':' << config_lineno << ": " << + "ERROR: unknown adaptation service option: " << + name << '=' << value); + return false; +} === modified file 'src/adaptation/ServiceConfig.h' --- src/adaptation/ServiceConfig.h 2010-08-09 08:23:45 +0000 +++ src/adaptation/ServiceConfig.h 2010-12-18 00:31:53 +0000 @@ -9,7 +9,7 @@ { // manages adaptation service configuration in squid.conf -class ServiceConfig +class ServiceConfig: public RefCountable { public: ServiceConfig(); @@ -42,6 +42,8 @@ /// interpret parsed values bool grokBool(bool &var, const char *name, const char *value); bool grokUri(const char *value); + /// handle name=value configuration option with name unknown to Squid + virtual bool grokExtension(const char *name, const char *value); }; } // namespace Adaptation === modified file 'src/adaptation/ecap/Config.cc' --- src/adaptation/ecap/Config.cc 2010-05-27 00:51:44 +0000 +++ src/adaptation/ecap/Config.cc 2011-03-08 23:56:22 +0000 @@ -26,10 +26,25 @@ CheckUnusedAdapterServices(AllServices()); } +Adaptation::ServiceConfig * +Adaptation::Ecap::Config::newServiceConfig() const +{ + return new ServiceConfig(); +} + Adaptation::ServicePointer -Adaptation::Ecap::Config::createService(const Adaptation::ServiceConfig &cfg) -{ - Adaptation::ServicePointer s = new Adaptation::Ecap::ServiceRep(cfg); - return s.getRaw(); +Adaptation::Ecap::Config::createService(const ServiceConfigPointer &cfg) +{ + return new Adaptation::Ecap::ServiceRep(cfg); +} + + +/* ServiceConfig */ + +bool +Adaptation::Ecap::ServiceConfig::grokExtension(const char *name, const char *value) +{ + extensions.push_back(std::make_pair(name, value)); + return true; } === modified file 'src/adaptation/ecap/Config.h' --- src/adaptation/ecap/Config.h 2010-05-26 03:06:02 +0000 +++ src/adaptation/ecap/Config.h 2011-03-08 23:56:22 +0000 @@ -7,12 +7,29 @@ #define SQUID_ECAP_CONFIG_H #include "adaptation/Config.h" +#include "adaptation/ServiceConfig.h" +#include +#include namespace Adaptation { namespace Ecap { +/// eCAP service configuration +class ServiceConfig: public Adaptation::ServiceConfig { +public: + // Adaptation::ServiceConfig API + virtual bool grokExtension(const char *name, const char *value); + +public: + typedef std::pair Extension; // name=value in cfg + typedef std::list Extensions; + Extensions extensions; +}; + + +/// General eCAP configuration class Config: public Adaptation::Config { @@ -22,11 +39,14 @@ virtual void finalize(); +protected: + virtual Adaptation::ServiceConfig *newServiceConfig() const; + private: Config(const Config &); // not implemented Config &operator =(const Config &); // not implemented - virtual Adaptation::ServicePointer createService(const Adaptation::ServiceConfig &cfg); + virtual Adaptation::ServicePointer createService(const ServiceConfigPointer &cfg); }; extern Config TheConfig; === modified file 'src/adaptation/ecap/Host.cc' --- src/adaptation/ecap/Host.cc 2011-02-07 04:16:22 +0000 +++ src/adaptation/ecap/Host.cc 2011-02-21 04:46:17 +0000 @@ -18,6 +18,7 @@ #if USE_HTCP const libecap::Name Adaptation::Ecap::protocolHtcp("Htcp", libecap::Name::NextId()); #endif +const libecap::Name Adaptation::Ecap::metaBypassable("bypassable", libecap::Name::NextId()); /// the host application (i.e., Squid) wrapper registered with libecap static libecap::shared_ptr TheHost; @@ -27,8 +28,12 @@ // assign our host-specific IDs to well-known names // this code can run only once + libecap::headerTransferEncoding.assignHostId(HDR_TRANSFER_ENCODING); libecap::headerReferer.assignHostId(HDR_REFERER); libecap::headerContentLength.assignHostId(HDR_CONTENT_LENGTH); + libecap::headerVia.assignHostId(HDR_VIA); + // TODO: libecap::headerXClientIp.assignHostId(HDR_X_CLIENT_IP); + // TODO: libecap::headerXServerIp.assignHostId(HDR_X_SERVER_IP); libecap::protocolHttp.assignHostId(PROTO_HTTP); libecap::protocolHttps.assignHostId(PROTO_HTTPS); @@ -43,6 +48,9 @@ #if USE_HTCP protocolHtcp.assignHostId(PROTO_HTCP); #endif + + // allows adapter to safely ignore this in adapter::Service::configure() + metaBypassable.assignHostId(1); } std::string @@ -74,7 +82,7 @@ return DBG_DATA; // is it a good idea to ignore other flags? if (lv.application()) - return DBG_DATA; // is it a good idea to ignore other flags? + return DBG_IMPORTANT; // is it a good idea to ignore other flags? return 2 + 2*lv.debugging() + 3*lv.operation() + 2*lv.xaction(); } === modified file 'src/adaptation/ecap/Host.h' --- src/adaptation/ecap/Host.h 2010-12-07 19:10:11 +0000 +++ src/adaptation/ecap/Host.h 2011-02-17 19:27:54 +0000 @@ -47,6 +47,7 @@ #if USE_HTCP extern const libecap::Name protocolHtcp; #endif +extern const libecap::Name metaBypassable; ///< an ecap_service parameter } // namespace Ecap } // namespace Adaptation === modified file 'src/adaptation/ecap/Makefile.am' --- src/adaptation/ecap/Makefile.am 2009-02-19 22:35:50 +0000 +++ src/adaptation/ecap/Makefile.am 2011-03-08 23:56:22 +0000 @@ -1,9 +1,9 @@ include $(top_srcdir)/src/Common.am include $(top_srcdir)/src/TestHeaders.am -noinst_LTLIBRARIES = libecap.la +noinst_LTLIBRARIES = libxecap.la -libecap_la_SOURCES = \ +libxecap_la_SOURCES = \ Config.h \ Config.cc \ Host.h \ @@ -16,3 +16,11 @@ XactionRep.cc \ \ Registry.h + +# add libecap using its pkg-config-produced configuration variables +libxecap_la_CXXFLAGS = $(EXTLIBECAP_CFLAGS) + +## It is tempting to put libxecap_la_LDFLAGS/LIBADD here, but it leads to weird +## linking errors. For example, "make clean all" works, but rebuilding after +## modifying a single source file leads to libtool's "file not found" errors. +## libxecap_la_LIBADD = $(EXTLIBECAP_LIBS) === modified file 'src/adaptation/ecap/MessageRep.cc' --- src/adaptation/ecap/MessageRep.cc 2011-02-08 01:12:42 +0000 +++ src/adaptation/ecap/MessageRep.cc 2011-02-18 23:58:13 +0000 @@ -8,6 +8,7 @@ #include #include #include +#include #include "adaptation/ecap/MessageRep.h" #include "adaptation/ecap/XactionRep.h" #include "adaptation/ecap/Host.h" /* for protocol constants */ @@ -66,6 +67,17 @@ theMessage.content_length = theHeader.getInt64(HDR_CONTENT_LENGTH); } +void +Adaptation::Ecap::HeaderRep::visitEach(libecap::NamedValueVisitor &visitor) const +{ + HttpHeaderPos pos = HttpHeaderInitPos; + while (HttpHeaderEntry *e = theHeader.getEntry(&pos)) { + const Name name(e->name.termedBuf()); // optimize: find std Names + name.assignHostId(e->id); + visitor.visit(name, Value(e->value.rawBuf(), e->value.size())); + } +} + libecap::Area Adaptation::Ecap::HeaderRep::image() const { === modified file 'src/adaptation/ecap/MessageRep.h' --- src/adaptation/ecap/MessageRep.h 2010-11-21 04:40:05 +0000 +++ src/adaptation/ecap/MessageRep.h 2010-12-16 15:41:37 +0000 @@ -41,6 +41,8 @@ virtual void add(const Name &name, const Value &value); virtual void removeAny(const Name &name); + virtual void visitEach(libecap::NamedValueVisitor &visitor) const; + virtual Area image() const; virtual void parse(const Area &buf); // throws on failures === modified file 'src/adaptation/ecap/ServiceRep.cc' --- src/adaptation/ecap/ServiceRep.cc 2010-08-24 00:12:54 +0000 +++ src/adaptation/ecap/ServiceRep.cc 2011-03-08 23:56:22 +0000 @@ -4,6 +4,11 @@ #include "squid.h" #include #include +#include +#include +#include +#include "adaptation/ecap/Config.h" +#include "adaptation/ecap/Host.h" #include "adaptation/ecap/ServiceRep.h" #include "adaptation/ecap/XactionRep.h" #include "base/TextException.h" @@ -11,7 +16,69 @@ // configured eCAP service wrappers static std::list TheServices; -Adaptation::Ecap::ServiceRep::ServiceRep(const Adaptation::ServiceConfig &cfg): +namespace Adaptation +{ +namespace Ecap +{ + +/// wraps Adaptation::Ecap::ServiceConfig to allow eCAP visitors +class ConfigRep: public libecap::Options +{ +public: + typedef Adaptation::Ecap::ServiceConfig Master; + typedef libecap::Name Name; + typedef libecap::Area Area; + + ConfigRep(const Master &aMaster); + + // libecap::Options API + virtual const libecap::Area option(const libecap::Name &name) const; + virtual void visitEachOption(libecap::NamedValueVisitor &visitor) const; + + const Master &master; ///< the configuration being wrapped +}; + +} // namespace Ecap +} // namespace Adaptation + + +Adaptation::Ecap::ConfigRep::ConfigRep(const Master &aMaster): master(aMaster) +{ +} + +const libecap::Area +Adaptation::Ecap::ConfigRep::option(const libecap::Name &name) const +{ + // we may supply the params we know about, but only when names have host ID + if (name == metaBypassable) + return Area(master.bypass ? "1" : "0", 1); + + // TODO: We could build a by-name index, but is it worth it? Good adapters + // should use visitEachOption() instead, to check for name typos/errors. + typedef Master::Extensions::const_iterator MECI; + for (MECI i = master.extensions.begin(); i != master.extensions.end(); ++i) { + if (name == i->first) + return Area(i->second.data(), i->second.size()); + } + + return Area(); +} + +void +Adaptation::Ecap::ConfigRep::visitEachOption(libecap::NamedValueVisitor &visitor) const +{ + // we may supply the params we know about too, but only if we set host ID + visitor.visit(metaBypassable, Area(master.bypass ? "1" : "0", 1)); + + // visit adapter-specific options (i.e., those not recognized by Squid) + typedef Master::Extensions::const_iterator MECI; + for (MECI i = master.extensions.begin(); i != master.extensions.end(); ++i) + visitor.visit(Name(i->first), Area::FromTempString(i->second)); +} + + + +Adaptation::Ecap::ServiceRep::ServiceRep(const ServiceConfigPointer &cfg): /*AsyncJob("Adaptation::Ecap::ServiceRep"),*/ Adaptation::Service(cfg), isDetached(false) { @@ -32,6 +99,10 @@ Adaptation::Service::finalize(); theService = FindAdapterService(cfg().uri); if (theService) { + debugs(93,3, HERE << "configuring eCAP service: " << theService->uri()); + const ConfigRep cfgRep(dynamic_cast(cfg())); + theService->configure(cfgRep); + debugs(93,3, HERE << "starting eCAP service: " << theService->uri()); theService->start(); } else { === modified file 'src/adaptation/ecap/ServiceRep.h' --- src/adaptation/ecap/ServiceRep.h 2010-08-23 23:15:26 +0000 +++ src/adaptation/ecap/ServiceRep.h 2011-03-08 23:56:22 +0000 @@ -23,7 +23,7 @@ class ServiceRep : public Adaptation::Service { public: - ServiceRep(const Adaptation::ServiceConfig &config); + explicit ServiceRep(const ServiceConfigPointer &aConfig); virtual ~ServiceRep(); typedef libecap::shared_ptr AdapterService; === modified file 'src/adaptation/ecap/XactionRep.cc' --- src/adaptation/ecap/XactionRep.cc 2010-12-12 23:29:26 +0000 +++ src/adaptation/ecap/XactionRep.cc 2011-02-18 23:58:13 +0000 @@ -4,16 +4,38 @@ #include "squid.h" #include #include +#include +#include #include #include "HttpRequest.h" #include "HttpReply.h" #include "SquidTime.h" #include "adaptation/ecap/XactionRep.h" +#include "adaptation/ecap/Config.h" +#include "adaptation/Initiator.h" #include "base/TextException.h" CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Ecap::XactionRep, XactionRep); +/// a libecap Visitor for converting adapter transaction options to HttpHeader +class OptionsExtractor: public libecap::NamedValueVisitor +{ +public: + typedef libecap::Name Name; + typedef libecap::Area Area; + + OptionsExtractor(HttpHeader &aMeta): meta(aMeta) {} + + // libecap::NamedValueVisitor API + virtual void visit(const Name &name, const Area &value) + { + meta.putExt(name.image().c_str(), value.toString().c_str()); + } + + HttpHeader &meta; ///< where to put extracted options +}; + Adaptation::Ecap::XactionRep::XactionRep( HttpMsg *virginHeader, HttpRequest *virginCause, const Adaptation::ServicePointer &aService): @@ -21,9 +43,9 @@ Adaptation::Initiate("Adaptation::Ecap::XactionRep"), theService(aService), theVirginRep(virginHeader), theCauseRep(NULL), - proxyingVb(opUndecided), proxyingAb(opUndecided), + makingVb(opUndecided), proxyingAb(opUndecided), adaptHistoryId(-1), - canAccessVb(false), + vbProductionFinished(false), abProductionFinished(false), abProductionAtEnd(false) { if (virginCause) @@ -52,15 +74,99 @@ return *theService; } +const libecap::Area +Adaptation::Ecap::XactionRep::option(const libecap::Name &name) const +{ + if (name == libecap::metaClientIp) + return clientIpValue(); + if (name == libecap::metaUserName) + return usernameValue(); + if (name == Adaptation::Config::masterx_shared_name) + return masterxSharedValue(name); + + // TODO: metaServerIp, metaAuthenticatedUser, and metaAuthenticatedGroups + return libecap::Area(); +} + +void +Adaptation::Ecap::XactionRep::visitEachOption(libecap::NamedValueVisitor &visitor) const +{ + if (const libecap::Area value = clientIpValue()) + visitor.visit(libecap::metaClientIp, value); + if (const libecap::Area value = usernameValue()) + visitor.visit(libecap::metaUserName, value); + + if (Adaptation::Config::masterx_shared_name) { + const libecap::Name name(Adaptation::Config::masterx_shared_name); + if (const libecap::Area value = masterxSharedValue(name)) + visitor.visit(name, value); + } + + // TODO: metaServerIp, metaAuthenticatedUser, and metaAuthenticatedGroups +} + +const libecap::Area +Adaptation::Ecap::XactionRep::clientIpValue() const +{ + const HttpRequest *request = dynamic_cast(theCauseRep ? + theCauseRep->raw().header : theVirginRep.raw().header); + Must(request); + // TODO: move this logic into HttpRequest::clientIp(bool) and + // HttpRequest::clientIpString(bool) and reuse everywhere + if (TheConfig.send_client_ip && request) { + Ip::Address client_addr; +#if FOLLOW_X_FORWARDED_FOR + if (TheConfig.use_indirect_client) { + client_addr = request->indirect_client_addr; + } else +#endif + client_addr = request->client_addr; + if (!client_addr.IsAnyAddr() && !client_addr.IsNoAddr()) { + char ntoabuf[MAX_IPSTRLEN] = ""; + client_addr.NtoA(ntoabuf,MAX_IPSTRLEN); + return libecap::Area::FromTempBuffer(ntoabuf, strlen(ntoabuf)); + } + } + return libecap::Area(); +} + +const libecap::Area +Adaptation::Ecap::XactionRep::usernameValue() const +{ + const HttpRequest *request = dynamic_cast(theCauseRep ? + theCauseRep->raw().header : theVirginRep.raw().header); + Must(request); + if (request->auth_user_request != NULL) { + if (char const *name = request->auth_user_request->username()) + return libecap::Area::FromTempBuffer(name, strlen(name)); + } + return libecap::Area(); +} + +const libecap::Area +Adaptation::Ecap::XactionRep::masterxSharedValue(const libecap::Name &name) const +{ + const HttpRequest *request = dynamic_cast(theCauseRep ? + theCauseRep->raw().header : theVirginRep.raw().header); + Must(request); + if (name.known()) { // must check to avoid empty names matching unset cfg + Adaptation::History::Pointer ah = request->adaptHistory(false); + if (ah != NULL) { + String name, value; + if (ah->getXxRecord(name, value)) + return libecap::Area::FromTempBuffer(value.rawBuf(), value.size()); + } + } + return libecap::Area(); +} + void Adaptation::Ecap::XactionRep::start() { Must(theMaster); - if (theVirginRep.raw().body_pipe != NULL) - canAccessVb = true; /// assumes nobody is consuming; \todo check - else - proxyingVb = opNever; + if (!theVirginRep.raw().body_pipe) + makingVb = opNever; // there is nothing to deliver const HttpRequest *request = dynamic_cast (theCauseRep ? theCauseRep->raw().header : theVirginRep.raw().header); @@ -88,13 +194,9 @@ } } - if (proxyingVb == opOn) { - BodyPipe::Pointer body_pipe = theVirginRep.raw().body_pipe; - if (body_pipe != NULL) { - Must(body_pipe->stillConsuming(this)); - stopConsumingFrom(body_pipe); - } - } + BodyPipe::Pointer &body_pipe = theVirginRep.raw().body_pipe; + if (body_pipe != NULL && body_pipe->stillConsuming(this)) + stopConsumingFrom(body_pipe); terminateMaster(); @@ -149,26 +251,54 @@ bool Adaptation::Ecap::XactionRep::doneAll() const { - return proxyingVb >= opComplete && proxyingAb >= opComplete && + return makingVb >= opComplete && proxyingAb >= opComplete && Adaptation::Initiate::doneAll(); } -// stops receiving virgin and enables auto-consumption -void -Adaptation::Ecap::XactionRep::dropVirgin(const char *reason) -{ - debugs(93,4, HERE << "because " << reason << "; status:" << status()); - Must(proxyingVb = opOn); +// stops receiving virgin and enables auto-consumption, dropping any vb bytes +void +Adaptation::Ecap::XactionRep::sinkVb(const char *reason) +{ + debugs(93,4, HERE << "sink for " << reason << "; status:" << status()); + + // we reset raw().body_pipe when we are done, so use this one for checking + const BodyPipePointer &permPipe = theVirginRep.raw().header->body_pipe; + if (permPipe != NULL) + permPipe->enableAutoConsumption(); + + forgetVb(reason); +} + +// stops receiving virgin but preserves it for others to use +void +Adaptation::Ecap::XactionRep::preserveVb(const char *reason) +{ + debugs(93,4, HERE << "preserve for " << reason << "; status:" << status()); + + // we reset raw().body_pipe when we are done, so use this one for checking + const BodyPipePointer &permPipe = theVirginRep.raw().header->body_pipe; + if (permPipe != NULL) { + // if libecap consumed, we cannot preserve + Must(!permPipe->consumedSize()); + } + + forgetVb(reason); +} + +// disassociates us from vb; the last step of sinking or preserving vb +void +Adaptation::Ecap::XactionRep::forgetVb(const char *reason) +{ + debugs(93,9, HERE << "forget vb " << reason << "; status:" << status()); BodyPipePointer &p = theVirginRep.raw().body_pipe; - Must(p != NULL); - Must(p->stillConsuming(this)); - stopConsumingFrom(p); - p->enableAutoConsumption(); - proxyingVb = opComplete; - canAccessVb = false; + if (p != NULL && p->stillConsuming(this)) + stopConsumingFrom(p); - // called from adapter handler so does not inform adapter + if (makingVb == opUndecided) + makingVb = opNever; + else if (makingVb == opOn) + makingVb = opComplete; } void @@ -178,25 +308,14 @@ Must(proxyingAb == opUndecided); proxyingAb = opNever; - BodyPipePointer &vbody_pipe = theVirginRep.raw().body_pipe; + preserveVb("useVirgin"); HttpMsg *clone = theVirginRep.raw().header->clone(); // check that clone() copies the pipe so that we do not have to - Must(!vbody_pipe == !clone->body_pipe); - - if (proxyingVb == opOn) { - Must(vbody_pipe->stillConsuming(this)); - // if libecap consumed, we cannot shortcircuit - Must(!vbody_pipe->consumedSize()); - stopConsumingFrom(vbody_pipe); - canAccessVb = false; - proxyingVb = opComplete; - } else if (proxyingVb == opUndecided) { - vbody_pipe = NULL; // it is not our pipe anymore - proxyingVb = opNever; - } - - sendAnswer(clone); + Must(!theVirginRep.raw().header->body_pipe == !clone->body_pipe); + + updateHistory(); + sendAnswer(Answer::Forward(clone)); Must(done()); } @@ -211,7 +330,8 @@ HttpMsg *msg = answer().header; if (!theAnswerRep->body()) { // final, bodyless answer proxyingAb = opNever; - sendAnswer(msg); + updateHistory(); + sendAnswer(Answer::Forward(msg)); } else { // got answer headers but need to handle body proxyingAb = opOn; Must(!msg->body_pipe); // only host can set body pipes @@ -220,7 +340,8 @@ rep->tieBody(this); // sets us as a producer Must(msg->body_pipe != NULL); // check tieBody - sendAnswer(msg); + updateHistory(); + sendAnswer(Answer::Forward(msg)); debugs(93,4, HERE << "adapter will produce body" << status()); theMaster->abMake(); // libecap will produce @@ -228,46 +349,109 @@ } void +Adaptation::Ecap::XactionRep::blockVirgin() +{ + debugs(93,3, HERE << status()); + Must(proxyingAb == opUndecided); + proxyingAb = opNever; + + sinkVb("blockVirgin"); + + updateHistory(); + sendAnswer(Answer::Block(service().cfg().key)); + Must(done()); +} + +/// Called just before sendAnswer() to record adapter meta-information +/// which may affect answer processing and may be needed for logging. +void +Adaptation::Ecap::XactionRep::updateHistory() +{ + if (!theMaster) // all updates rely on being able to query the adapter + return; + + const HttpRequest *request = dynamic_cast(theCauseRep ? + theCauseRep->raw().header : theVirginRep.raw().header); + Must(request); + + // TODO: move common ICAP/eCAP logic to Adaptation::Xaction or similar + // TODO: optimize Area-to-String conversion + + // update the cross-transactional database if needed + if (const char *xxNameStr = Adaptation::Config::masterx_shared_name) { + Adaptation::History::Pointer ah = request->adaptHistory(true); + if (ah != NULL) { + libecap::Name xxName(xxNameStr); // TODO: optimize? + if (const libecap::Area val = theMaster->option(xxName)) + ah->updateXxRecord(xxNameStr, val.toString().c_str()); + } + } + + // update the adaptation plan if needed + if (service().cfg().routing) { + String services; + if (const libecap::Area services = theMaster->option(libecap::metaNextServices)) { + Adaptation::History::Pointer ah = request->adaptHistory(true); + if (ah != NULL) + ah->updateNextServices(services.toString().c_str()); + } + } // TODO: else warn (occasionally!) if we got libecap::metaNextServices + + // Store received meta headers for adapt::adaptLogHistory(); + if (ah != NULL) { + HttpHeader meta(hoReply); + OptionsExtractor extractor(meta); + theMaster->visitEachOption(extractor); + ah->recordMeta(&meta); + } +} + + +void Adaptation::Ecap::XactionRep::vbDiscard() { - Must(proxyingVb == opUndecided); + Must(makingVb == opUndecided); // if adapter does not need vb, we do not need to send it - dropVirgin("vbDiscard"); - Must(proxyingVb == opNever); + sinkVb("vbDiscard"); + Must(makingVb == opNever); } void Adaptation::Ecap::XactionRep::vbMake() { - Must(proxyingVb == opUndecided); + Must(makingVb == opUndecided); BodyPipePointer &p = theVirginRep.raw().body_pipe; Must(p != NULL); - Must(p->setConsumerIfNotLate(this)); // to make vb, we must receive vb - proxyingVb = opOn; + Must(p->setConsumerIfNotLate(this)); // to deliver vb, we must receive vb + makingVb = opOn; } void Adaptation::Ecap::XactionRep::vbStopMaking() { + Must(makingVb == opOn); // if adapter does not need vb, we do not need to receive it - if (proxyingVb == opOn) - dropVirgin("vbStopMaking"); - Must(proxyingVb == opComplete); + sinkVb("vbStopMaking"); + Must(makingVb == opComplete); } void Adaptation::Ecap::XactionRep::vbMakeMore() { - Must(proxyingVb == opOn); // cannot make more if done proxying + Must(makingVb == opOn); // cannot make more if done proxying // we cannot guarantee more vb, but we can check that there is a chance - Must(!theVirginRep.raw().body_pipe->exhausted()); + const BodyPipePointer &p = theVirginRep.raw().body_pipe; + Must(p != NULL && p->stillConsuming(this)); // we are plugged in + Must(!p->productionEnded() && p->mayNeedMoreData()); // and may get more } libecap::Area Adaptation::Ecap::XactionRep::vbContent(libecap::size_type o, libecap::size_type s) { - Must(canAccessVb); - // We may not be proxyingVb yet. It should be OK, but see vbContentShift(). + // We may not be makingVb yet. It should be OK, but see vbContentShift(). const BodyPipePointer &p = theVirginRep.raw().body_pipe; Must(p != NULL); @@ -291,8 +475,7 @@ void Adaptation::Ecap::XactionRep::vbContentShift(libecap::size_type n) { - Must(canAccessVb); - // We may not be proxyingVb yet. It should be OK now, but if BodyPipe + // We may not be makingVb yet. It should be OK now, but if BodyPipe // consume() requirements change, we would have to return empty vbContent // until the adapter registers as a consumer @@ -372,7 +555,7 @@ void Adaptation::Ecap::XactionRep::noteMoreBodyDataAvailable(RefCount bp) { - Must(proxyingVb == opOn); + Must(makingVb == opOn); // or we would not be registered as a consumer Must(theMaster); theMaster->noteVbContentAvailable(); } @@ -380,19 +563,19 @@ void Adaptation::Ecap::XactionRep::noteBodyProductionEnded(RefCount bp) { - Must(proxyingVb == opOn); + Must(makingVb == opOn); // or we would not be registered as a consumer Must(theMaster); theMaster->noteVbContentDone(true); - proxyingVb = opComplete; + vbProductionFinished = true; } void Adaptation::Ecap::XactionRep::noteBodyProducerAborted(RefCount bp) { - Must(proxyingVb == opOn); + Must(makingVb == opOn); // or we would not be registered as a consumer Must(theMaster); theMaster->noteVbContentDone(false); - proxyingVb = opComplete; + vbProductionFinished = true; } void @@ -426,24 +609,34 @@ buf.append(" [", 2); - if (proxyingVb == opOn) { - const BodyPipePointer &vp = theVirginRep.raw().body_pipe; - if (!canAccessVb) - buf.append("x", 1); - if (vp != NULL) { // XXX: but may not be stillConsuming() - buf.append("Vb", 2); - } else - buf.append("V.", 2); - } + if (makingVb) + buf.Printf("M%d", static_cast(makingVb)); + + const BodyPipePointer &vp = theVirginRep.raw().body_pipe; + if (!vp) + buf.append(" !V", 3); + else + if (vp->stillConsuming(const_cast(this))) + buf.append(" Vc", 3); + else + buf.append(" V?", 3); + + if (vbProductionFinished) + buf.append(".", 1); + + + buf.Printf(" A%d", static_cast(proxyingAb)); if (proxyingAb == opOn) { MessageRep *rep = dynamic_cast(theAnswerRep.get()); Must(rep); const BodyPipePointer &ap = rep->raw().body_pipe; - if (ap != NULL) { // XXX: but may not be stillProducing() - buf.append(" Ab", 3); - } else - buf.append(" A.", 3); + if (!ap) + buf.append(" !A", 3); + else if (ap->stillProducing(const_cast(this))) + buf.append(" Ap", 3); + else + buf.append(" A?", 3); } buf.Printf(" %s%u]", id.Prefix, id.value); === modified file 'src/adaptation/ecap/XactionRep.h' --- src/adaptation/ecap/XactionRep.h 2010-08-23 23:15:26 +0000 +++ src/adaptation/ecap/XactionRep.h 2011-02-18 19:39:05 +0000 @@ -35,11 +35,14 @@ void master(const AdapterXaction &aMaster); // establish a link // libecap::host::Xaction API + virtual const libecap::Area option(const libecap::Name &name) const; + virtual void visitEachOption(libecap::NamedValueVisitor &visitor) const; virtual libecap::Message &virgin(); virtual const libecap::Message &cause(); virtual libecap::Message &adapted(); virtual void useVirgin(); virtual void useAdapted(const libecap::shared_ptr &msg); + virtual void blockVirgin(); virtual void adaptationDelayed(const libecap::Delay &); virtual void adaptationAborted(); virtual void vbDiscard(); @@ -77,12 +80,20 @@ Adaptation::Message &answer(); - void dropVirgin(const char *reason); + void sinkVb(const char *reason); + void preserveVb(const char *reason); + void forgetVb(const char *reason); + void moveAbContent(); + void updateHistory(); void terminateMaster(); void scheduleStop(const char *reason); + const libecap::Area clientIpValue() const; + const libecap::Area usernameValue() const; + const libecap::Area masterxSharedValue(const libecap::Name &name) const; + private: AdapterXaction theMaster; // the actual adaptation xaction we represent Adaptation::ServicePointer theService; ///< xaction's adaptation service @@ -94,10 +105,10 @@ MessagePtr theAnswerRep; typedef enum { opUndecided, opOn, opComplete, opNever } OperationState; - OperationState proxyingVb; // delivering virgin body from core to adapter + OperationState makingVb; //< delivering virgin body from pipe to adapter OperationState proxyingAb; // delivering adapted body from adapter to core int adaptHistoryId; ///< adaptation history slot reservation - bool canAccessVb; // virgin BodyPipe content is accessible + bool vbProductionFinished; // whether there can be no more vb bytes bool abProductionFinished; // whether adapter has finished producing ab bool abProductionAtEnd; // whether adapter produced a complete ab === modified file 'src/adaptation/forward.h' --- src/adaptation/forward.h 2009-07-13 01:20:26 +0000 +++ src/adaptation/forward.h 2010-12-18 00:31:53 +0000 @@ -25,8 +25,10 @@ class ServicePlan; class ServiceFilter; class Message; +class Answer; typedef RefCount ServicePointer; +typedef RefCount ServiceConfigPointer; typedef RefCount ServiceGroupPointer; } // namespace Adaptation === modified file 'src/adaptation/icap/Config.cc' --- src/adaptation/icap/Config.cc 2010-06-14 20:01:59 +0000 +++ src/adaptation/icap/Config.cc 2011-03-08 23:56:22 +0000 @@ -58,7 +58,7 @@ } Adaptation::ServicePointer -Adaptation::Icap::Config::createService(const Adaptation::ServiceConfig &cfg) +Adaptation::Icap::Config::createService(const ServiceConfigPointer &cfg) { return new Adaptation::Icap::ServiceRep(cfg); } === modified file 'src/adaptation/icap/Config.h' --- src/adaptation/icap/Config.h 2010-06-14 20:01:59 +0000 +++ src/adaptation/icap/Config.h 2011-03-08 23:56:22 +0000 @@ -76,7 +76,7 @@ Config(const Config &); // not implemented Config &operator =(const Config &); // not implemented - virtual Adaptation::ServicePointer createService(const Adaptation::ServiceConfig &cfg); + virtual Adaptation::ServicePointer createService(const ServiceConfigPointer &cfg); }; extern Config TheConfig; === modified file 'src/adaptation/icap/History.cc' --- src/adaptation/icap/History.cc 2010-11-20 11:31:38 +0000 +++ src/adaptation/icap/History.cc 2011-02-18 19:39:05 +0000 @@ -3,68 +3,12 @@ #include "globals.h" #include "SquidTime.h" -Adaptation::Icap::History::History(): mergeOfIcapHeaders(hoRequest), - lastIcapHeader(hoRequest), logType(LOG_TAG_NONE), req_sz(0), +Adaptation::Icap::History::History(): + logType(LOG_TAG_NONE), req_sz(0), pastTime(0), concurrencyLevel(0) { } -Adaptation::Icap::History::History(const Adaptation::Icap::History& ih) -{ - assign(ih); -} - -Adaptation::Icap::History::~History() -{ - mergeOfIcapHeaders.clean(); - lastIcapHeader.clean(); - rfc931.clean(); -#if USE_SSL - ssluser.clean(); -#endif - log_uri.clean(); -} - -void Adaptation::Icap::History::assign(const Adaptation::Icap::History& ih) -{ - mergeOfIcapHeaders.clean(); - mergeOfIcapHeaders.update(&ih.mergeOfIcapHeaders, NULL); - lastIcapHeader.clean(); - lastIcapHeader.update(&ih.lastIcapHeader, NULL); - rfc931 = ih.rfc931; - -#if USE_SSL - ssluser = ih.ssluser; -#endif - - logType = ih.logType; - log_uri = ih.log_uri; - req_sz = ih.req_sz; - pastTime = ih.pastTime; - currentStart = ih.currentStart; - concurrencyLevel = ih.concurrencyLevel; - debugs(93,7, HERE << this << " = " << &ih); -} - -Adaptation::Icap::History& Adaptation::Icap::History::operator=(const History& ih) -{ - if (this != &ih) - assign(ih); - return *this; -} - -void Adaptation::Icap::History::setIcapLastHeader(const HttpHeader * lih) -{ - lastIcapHeader.clean(); - lastIcapHeader.update(lih, NULL); -} - -void Adaptation::Icap::History::mergeIcapHeaders(const HttpHeader * lih) -{ - mergeOfIcapHeaders.update(lih, NULL); - mergeOfIcapHeaders.compact(); -} - void Adaptation::Icap::History::start(const char *context) { if (!concurrencyLevel++) === modified file 'src/adaptation/icap/History.h' --- src/adaptation/icap/History.h 2009-08-23 09:30:49 +0000 +++ src/adaptation/icap/History.h 2011-02-18 19:39:05 +0000 @@ -2,7 +2,6 @@ #define SQUID_ICAPHISTORY_H #include "RefCount.h" -#include "HttpHeader.h" #include "enums.h" namespace Adaptation @@ -17,16 +16,6 @@ typedef RefCount Pointer; History(); - History(const History& ih); - - ~History(); - - History& operator=(const History& ih); - - ///store the last reply header from ICAP server - void setIcapLastHeader(const HttpHeader * lih); - ///merge new header and stored one - void mergeIcapHeaders(const HttpHeader * lih); /// record the start of an ICAP processing interval void start(const char *context); @@ -36,8 +25,6 @@ /// returns the total time of all ICAP processing intervals int processingTime() const; - HttpHeader mergeOfIcapHeaders; ///< Merge of REQMOD and RESPMOD responses. If both responses contain the header, the one from the last response will be used - HttpHeader lastIcapHeader; ///< Last received reply from ICAP server String rfc931; ///< the username from ident #if USE_SSL String ssluser; ///< the username from SSL @@ -48,8 +35,6 @@ size_t req_sz; ///< the request size private: - void assign(const History &); - int currentTime() const; ///< time since current start or zero timeval currentStart; ///< when the current processing interval started === modified file 'src/adaptation/icap/Launcher.cc' --- src/adaptation/icap/Launcher.cc 2010-08-24 00:12:54 +0000 +++ src/adaptation/icap/Launcher.cc 2010-12-15 17:52:35 +0000 @@ -50,12 +50,16 @@ Must(initiated(theXaction)); } -void Adaptation::Icap::Launcher::noteAdaptationAnswer(HttpMsg *message) +void Adaptation::Icap::Launcher::noteAdaptationAnswer(const Answer &answer) { - sendAnswer(message); + debugs(93,5, HERE << "launches: " << theLaunches << " answer: " << answer); + + // XXX: akError is unused by ICAPXaction in favor of noteXactAbort() + Must(answer.kind != Answer::akError); + + sendAnswer(answer); clearAdaptation(theXaction); Must(done()); - debugs(93,3, HERE << "Adaptation::Icap::Launcher::noteAdaptationAnswer exiting "); } void Adaptation::Icap::Launcher::noteInitiatorAborted() @@ -67,15 +71,6 @@ } -// XXX: this call is unused by ICAPXaction in favor of ICAPLauncher::noteXactAbort -void Adaptation::Icap::Launcher::noteAdaptationQueryAbort(bool final) -{ - debugs(93,5, HERE << "launches: " << theLaunches << "; final: " << final); - clearAdaptation(theXaction); - - Must(done()); // swanSong will notify the initiator -} - void Adaptation::Icap::Launcher::noteXactAbort(XactAbortInfo info) { debugs(93,5, HERE << "theXaction:" << theXaction << " launches: " << theLaunches); === modified file 'src/adaptation/icap/Launcher.h' --- src/adaptation/icap/Launcher.h 2010-08-24 00:12:54 +0000 +++ src/adaptation/icap/Launcher.h 2010-12-15 17:52:35 +0000 @@ -80,13 +80,12 @@ void noteInitiatorAborted(); // Adaptation::Initiator: asynchronous communication with the current transaction - virtual void noteAdaptationAnswer(HttpMsg *message); + virtual void noteAdaptationAnswer(const Answer &answer); virtual void noteXactAbort(XactAbortInfo info); private: bool canRetry(XactAbortInfo &info) const; //< true if can retry in the case of persistent connection failures bool canRepeat(XactAbortInfo &info) const; //< true if can repeat in the case of no or unsatisfactory response - virtual void noteAdaptationQueryAbort(bool final); protected: // Adaptation::Initiate API implementation === modified file 'src/adaptation/icap/ModXact.cc' --- src/adaptation/icap/ModXact.cc 2010-11-07 03:07:29 +0000 +++ src/adaptation/icap/ModXact.cc 2011-02-18 19:39:05 +0000 @@ -714,7 +714,7 @@ { disableRepeats("sent headers"); disableBypass("sent headers", true); - sendAnswer(adapted.header); + sendAnswer(Answer::Forward(adapted.header)); if (state.sending == State::sendingVirgin) echoMore(); @@ -770,7 +770,7 @@ // update the cross-transactional database if needed (all status codes!) if (const char *xxName = Adaptation::Config::masterx_shared_name) { Adaptation::History::Pointer ah = request->adaptHistory(true); - if (ah != NULL) { + if (ah != NULL) { // TODO: reorder checks to avoid creating history const String val = icapReply->header.getByName(xxName); if (val.size() > 0) // XXX: HttpHeader lacks empty value detection ah->updateXxRecord(xxName, val); @@ -791,11 +791,9 @@ // If we already have stored headers from previous ICAP transaction related to this // request, old headers will be replaced with the new one. - Adaptation::Icap::History::Pointer h = request->icapHistory(); - if (h != NULL) { - h->mergeIcapHeaders(&icapReply->header); - h->setIcapLastHeader(&icapReply->header); - } + Adaptation::History::Pointer ah = request->adaptLogHistory(); + if (ah != NULL) + ah->recordMeta(&icapReply->header); // handle100Continue() manages state.writing on its own. // Non-100 status means the server needs no postPreview data from us. @@ -1302,6 +1300,7 @@ // share the cross-transactional database records if needed if (Adaptation::Config::masterx_shared_name) { + // XXX: do not create history here: there can be no values in empty ah Adaptation::History::Pointer ah = request->adaptHistory(true); if (ah != NULL) { String name, value; @@ -1357,7 +1356,7 @@ if (TheConfig.send_client_ip && request) { Ip::Address client_addr; #if FOLLOW_X_FORWARDED_FOR - if (TheConfig.icap_uses_indirect_client) { + if (TheConfig.use_indirect_client) { client_addr = request->indirect_client_addr; } else #endif @@ -1366,7 +1365,7 @@ buf.Printf("X-Client-IP: %s\r\n", client_addr.NtoA(ntoabuf,MAX_IPSTRLEN)); } - if (TheConfig.send_client_username && request) + if (TheConfig.send_username && request) makeUsernameHeader(request, buf); // fprintf(stderr, "%s\n", buf.content()); === modified file 'src/adaptation/icap/OptXact.cc' --- src/adaptation/icap/OptXact.cc 2010-09-13 00:19:25 +0000 +++ src/adaptation/icap/OptXact.cc 2010-12-15 17:52:35 +0000 @@ -79,7 +79,7 @@ debugs(93, 7, HERE << "readAll=" << readAll); icap_tio_finish = current_time; setOutcome(xoOpt); - sendAnswer(icapReply); + sendAnswer(Answer::Forward(icapReply)); Must(done()); // there should be nothing else to do return; } === modified file 'src/adaptation/icap/ServiceRep.cc' --- src/adaptation/icap/ServiceRep.cc 2010-08-24 00:12:54 +0000 +++ src/adaptation/icap/ServiceRep.cc 2011-03-08 23:56:22 +0000 @@ -15,7 +15,7 @@ CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap, ServiceRep); -Adaptation::Icap::ServiceRep::ServiceRep(const Adaptation::ServiceConfig &svcCfg): +Adaptation::Icap::ServiceRep::ServiceRep(const ServiceConfigPointer &svcCfg): AsyncJob("Adaptation::Icap::ServiceRep"), Adaptation::Service(svcCfg), theOptions(NULL), theOptionsFetcher(0), theLastUpdate(0), isSuspended(0), notifying(false), @@ -304,11 +304,19 @@ } // we are receiving ICAP OPTIONS response headers here or NULL on failures -void Adaptation::Icap::ServiceRep::noteAdaptationAnswer(HttpMsg *msg) +void Adaptation::Icap::ServiceRep::noteAdaptationAnswer(const Answer &answer) { Must(initiated(theOptionsFetcher)); clearAdaptation(theOptionsFetcher); + if (answer.kind == Answer::akError) { + debugs(93,3, HERE << "failed to fetch options " << status()); + handleNewOptions(0); + return; + } + + Must(answer.kind == Answer::akForward); // no akBlock for OPTIONS requests + HttpMsg *msg = answer.message; Must(msg); debugs(93,5, HERE << "is interpreting new options " << status()); @@ -324,15 +332,6 @@ handleNewOptions(newOptions); } -void Adaptation::Icap::ServiceRep::noteAdaptationQueryAbort(bool) -{ - Must(initiated(theOptionsFetcher)); - clearAdaptation(theOptionsFetcher); - - debugs(93,3, HERE << "failed to fetch options " << status()); - handleNewOptions(0); -} - // we (a) must keep trying to get OPTIONS and (b) are RefCounted so we // must keep our job alive (XXX: until nobody needs us) void Adaptation::Icap::ServiceRep::callException(const std::exception &e) === modified file 'src/adaptation/icap/ServiceRep.h' --- src/adaptation/icap/ServiceRep.h 2010-08-23 23:15:26 +0000 +++ src/adaptation/icap/ServiceRep.h 2011-03-08 23:56:22 +0000 @@ -87,7 +87,7 @@ typedef RefCount Pointer; public: - ServiceRep(const Adaptation::ServiceConfig &config); + explicit ServiceRep(const ServiceConfigPointer &aConfig); virtual ~ServiceRep(); virtual void finalize(); @@ -119,8 +119,7 @@ void noteTimeToNotify(); // receive either an ICAP OPTIONS response header or an abort message - virtual void noteAdaptationAnswer(HttpMsg *msg); - virtual void noteAdaptationQueryAbort(bool); + virtual void noteAdaptationAnswer(const Answer &answer); private: // stores Prepare() callback info === modified file 'src/cf.data.pre' --- src/cf.data.pre 2011-02-07 10:27:53 +0000 +++ src/cf.data.pre 2011-03-08 23:56:22 +0000 @@ -2932,7 +2932,7 @@ sent to the first selected peer. The timer stops with the last I/O with the last peer. - If ICAP is enabled, the following two codes become available (as + If ICAP is enabled, the following code becomes available (as well as ICAP log codes documented with the icap_log option): icap::tt Total ICAP processing time for the HTTP @@ -2940,14 +2940,13 @@ ACLs are checked and when ICAP transaction is in progress. - icap::a %icap::to/%03icap::Hs %icap::icapHistory(); -#endif if (Config.onoff.log_mime_hdrs) { Packer p; MemBuf mb; @@ -575,14 +572,15 @@ aLogEntry->headers.request = xstrdup(mb.buf); } -#if ICAP_CLIENT - packerClean(&p); - mb.reset(); - packerToMemInit(&p, &mb); - - if (ih != NULL) - ih->lastIcapHeader.packInto(&p); - aLogEntry->headers.icap = xstrdup(mb.buf); +#if USE_ADAPTATION + const Adaptation::History::Pointer ah = request->adaptLogHistory(); + if (ah != NULL) { + packerClean(&p); + mb.reset(); + packerToMemInit(&p, &mb); + ah->lastMeta.packInto(&p); + aLogEntry->headers.adapt_last = xstrdup(mb.buf); + } #endif packerClean(&p); @@ -590,6 +588,7 @@ } #if ICAP_CLIENT + const Adaptation::Icap::History::Pointer ih = request->icapHistory(); if (ih != NULL) aLogEntry->icap.processingTime = ih->processingTime(); #endif === modified file 'src/client_side_request.cc' --- src/client_side_request.cc 2011-02-07 10:27:53 +0000 +++ src/client_side_request.cc 2011-02-18 23:58:13 +0000 @@ -1406,9 +1406,30 @@ } void -ClientHttpRequest::noteAdaptationAnswer(HttpMsg *msg) +ClientHttpRequest::noteAdaptationAnswer(const Adaptation::Answer &answer) { assert(cbdataReferenceValid(this)); // indicates bug + clearAdaptation(virginHeadSource); + assert(!adaptedBodySource); + + switch (answer.kind) { + case Adaptation::Answer::akForward: + handleAdaptedHeader(answer.message); + break; + + case Adaptation::Answer::akBlock: + handleAdaptationBlock(answer); + break; + + case Adaptation::Answer::akError: + handleAdaptationFailure(ERR_DETAIL_CLT_REQMOD_ABORT, !answer.final); + break; + } +} + +void +ClientHttpRequest::handleAdaptedHeader(HttpMsg *msg) +{ assert(msg); if (HttpRequest *new_req = dynamic_cast(msg)) { @@ -1457,11 +1478,13 @@ } void -ClientHttpRequest::noteAdaptationQueryAbort(bool final) +ClientHttpRequest::handleAdaptationBlock(const Adaptation::Answer &answer) { - clearAdaptation(virginHeadSource); - assert(!adaptedBodySource); - handleAdaptationFailure(ERR_DETAIL_CLT_REQMOD_ABORT, !final); + request->detailError(ERR_ACCESS_DENIED, ERR_DETAIL_REQMOD_BLOCK); + AclMatchedName = answer.ruleId.termedBuf(); + assert(calloutContext); + calloutContext->clientAccessCheckDone(ACCESS_DENIED); + AclMatchedName = NULL; } void === modified file 'src/client_side_request.h' --- src/client_side_request.h 2010-10-21 08:13:41 +0000 +++ src/client_side_request.h 2010-12-15 17:52:35 +0000 @@ -166,8 +166,9 @@ private: // Adaptation::Initiator API - virtual void noteAdaptationAnswer(HttpMsg *message); - virtual void noteAdaptationQueryAbort(bool final); + virtual void noteAdaptationAnswer(const Adaptation::Answer &answer); + void handleAdaptedHeader(HttpMsg *msg); + void handleAdaptationBlock(const Adaptation::Answer &answer); // BodyConsumer API, called by BodyPipe virtual void noteMoreBodyDataAvailable(BodyPipe::Pointer); === modified file 'src/err_detail_type.h' --- src/err_detail_type.h 2010-12-07 19:32:43 +0000 +++ src/err_detail_type.h 2010-12-15 17:52:35 +0000 @@ -9,6 +9,9 @@ ERR_DETAIL_CLT_REQMOD_RESP_BODY, // client-side detected REQMOD satisfaction reply body failure ERR_DETAIL_ICAP_RESPMOD_EARLY, // RESPMOD failed w/o store entry ERR_DETAIL_ICAP_RESPMOD_LATE, // RESPMOD failed with a store entry + ERR_DETAIL_REQMOD_BLOCK, // REQMOD denied client access + ERR_DETAIL_RESPMOD_BLOCK_EARLY, // RESPMOD denied client access to HTTP response, before any part of the response was sent + ERR_DETAIL_RESPMOD_BLOCK_LATE, // RESPMOD denied client access to HTTP response, after [a part of] the response was sent ERR_DETAIL_ICAP_XACT_START, // transaction start failure ERR_DETAIL_ICAP_XACT_BODY_CONSUMER_ABORT, // transaction body consumer gone ERR_DETAIL_ICAP_INIT_GONE, // initiator gone === modified file 'src/log/FormatSquidCustom.cc' --- src/log/FormatSquidCustom.cc 2010-12-13 01:52:37 +0000 +++ src/log/FormatSquidCustom.cc 2011-02-18 23:58:13 +0000 @@ -312,42 +312,43 @@ out = sb.termedBuf(); } break; + + case LFT_ADAPTATION_LAST_HEADER: + if (al->request) { + const Adaptation::History::Pointer ah = al->request->adaptHistory(); + if (ah != NULL) // XXX: add adapt::allMeta.getByName(fmt->data.header.header); + } + + // XXX: here and elsewhere: move such code inside the if guard + out = sb.termedBuf(); + + quote = 1; + + break; + + case LFT_ADAPTATION_LAST_HEADER_ELEM: + if (al->request) { + const Adaptation::History::Pointer ah = al->request->adaptHistory(); + if (ah != NULL) // XXX: add adapt::allMeta.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator); + } + + out = sb.termedBuf(); + + quote = 1; + + break; + + case LFT_ADAPTATION_LAST_ALL_HEADERS: + out = al->headers.adapt_last; + + quote = 1; + + break; #endif #if ICAP_CLIENT - case LFT_ICAP_LAST_MATCHED_HEADER: - if (al->request) { - Adaptation::Icap::History::Pointer ih = al->request->icapHistory(); - if (ih != NULL) - sb = ih->mergeOfIcapHeaders.getByName(fmt->data.header.header); - } - - out = sb.termedBuf(); - - quote = 1; - - break; - - case LFT_ICAP_LAST_MATCHED_HEADER_ELEM: - if (al->request) { - Adaptation::Icap::History::Pointer ih = al->request->icapHistory(); - if (ih != NULL) - sb = ih->mergeOfIcapHeaders.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator); - } - - out = sb.termedBuf(); - - quote = 1; - - break; - - case LFT_ICAP_LAST_MATCHED_ALL_HEADERS: - out = al->headers.icap; - - quote = 1; - - break; - case LFT_ICAP_ADDR: if (!out) out = al->icap.hostAddr.NtoA(tmp,1024); === modified file 'src/log/Tokens.cc' --- src/log/Tokens.cc 2010-12-12 05:30:58 +0000 +++ src/log/Tokens.cc 2011-02-18 23:58:13 +0000 @@ -161,11 +161,12 @@ #if USE_ADAPTATION {"adapt::all_trs", LTF_ADAPTATION_ALL_XACT_TIMES}, {"adapt::sum_trs", LTF_ADAPTATION_SUM_XACT_TIMES}, + {"adapt::type) { +#if USE_ADAPTATION + case LFT_ADAPTATION_LAST_HEADER: +#endif + #if ICAP_CLIENT - case LFT_ICAP_LAST_MATCHED_HEADER: - case LFT_ICAP_REQ_HEADER: case LFT_ICAP_REP_HEADER: @@ -376,10 +379,12 @@ case LFT_REPLY_HEADER: lt->type = LFT_REPLY_HEADER_ELEM; break; +#if USE_ADAPTATION + case LFT_ADAPTATION_LAST_HEADER: + lt->type = LFT_ADAPTATION_LAST_HEADER_ELEM; + break; +#endif #if ICAP_CLIENT - case LFT_ICAP_LAST_MATCHED_HEADER: - lt->type = LFT_ICAP_LAST_MATCHED_HEADER_ELEM; - break; case LFT_ICAP_REQ_HEADER: lt->type = LFT_ICAP_REQ_HEADER_ELEM; break; @@ -406,10 +411,12 @@ case LFT_REPLY_HEADER: lt->type = LFT_REPLY_ALL_HEADERS; break; +#if USE_ADAPTATION + case LFT_ADAPTATION_LAST_HEADER: + lt->type = LFT_ADAPTATION_LAST_ALL_HEADERS; + break; +#endif #if ICAP_CLIENT - case LFT_ICAP_LAST_MATCHED_HEADER: - lt->type = LFT_ICAP_LAST_MATCHED_ALL_HEADERS; - break; case LFT_ICAP_REQ_HEADER: lt->type = LFT_ICAP_REQ_ALL_HEADERS; break; @@ -510,8 +517,10 @@ case LFT_STRING: break; +#if USE_ADAPTATION + case LFT_ADAPTATION_LAST_HEADER_ELEM: +#endif #if ICAP_CLIENT - case LFT_ICAP_LAST_MATCHED_HEADER_ELEM: case LFT_ICAP_REQ_HEADER_ELEM: case LFT_ICAP_REP_HEADER_ELEM: #endif @@ -528,18 +537,20 @@ switch (type) { case LFT_REQUEST_HEADER_ELEM: - type = LFT_REQUEST_HEADER_ELEM; + type = LFT_REQUEST_HEADER_ELEM; // XXX: remove _ELEM? break; case LFT_ADAPTED_REQUEST_HEADER_ELEM: - type = LFT_ADAPTED_REQUEST_HEADER_ELEM; + type = LFT_ADAPTED_REQUEST_HEADER_ELEM; // XXX: remove _ELEM? break; case LFT_REPLY_HEADER_ELEM: - type = LFT_REPLY_HEADER_ELEM; - break; + type = LFT_REPLY_HEADER_ELEM; // XXX: remove _ELEM? + break; +#if USE_ADAPTATION + case LFT_ADAPTATION_LAST_HEADER_ELEM: + type = LFT_ADAPTATION_LAST_HEADER; + break; +#endif #if ICAP_CLIENT - case LFT_ICAP_LAST_MATCHED_HEADER_ELEM: - type = LFT_ICAP_LAST_MATCHED_HEADER; - break; case LFT_ICAP_REQ_HEADER_ELEM: type = LFT_ICAP_REQ_HEADER; break; @@ -557,8 +568,10 @@ case LFT_ADAPTED_REQUEST_ALL_HEADERS: case LFT_REPLY_ALL_HEADERS: +#if USE_ADAPTATION + case LFT_ADAPTATION_LAST_ALL_HEADERS: +#endif #if ICAP_CLIENT - case LFT_ICAP_LAST_MATCHED_ALL_HEADERS: case LFT_ICAP_REQ_ALL_HEADERS: case LFT_ICAP_REP_ALL_HEADERS: #endif @@ -573,10 +586,12 @@ case LFT_REPLY_ALL_HEADERS: type = LFT_REPLY_HEADER; break; +#if USE_ADAPTATION + case LFT_ADAPTATION_LAST_ALL_HEADERS: + type = LFT_ADAPTATION_LAST_HEADER; + break; +#endif #if ICAP_CLIENT - case LFT_ICAP_LAST_MATCHED_ALL_HEADERS: - type = LFT_ICAP_LAST_MATCHED_HEADER; - break; case LFT_ICAP_REQ_ALL_HEADERS: type = LFT_ICAP_REQ_HEADER; break; === modified file 'src/log/Tokens.h' --- src/log/Tokens.h 2010-12-12 05:30:58 +0000 +++ src/log/Tokens.h 2011-02-18 23:58:13 +0000 @@ -132,14 +132,14 @@ #if USE_ADAPTATION LTF_ADAPTATION_SUM_XACT_TIMES, LTF_ADAPTATION_ALL_XACT_TIMES, + LFT_ADAPTATION_LAST_HEADER, + LFT_ADAPTATION_LAST_HEADER_ELEM, + LFT_ADAPTATION_LAST_ALL_HEADERS, #endif #if ICAP_CLIENT LFT_ICAP_TOTAL_TIME, - LFT_ICAP_LAST_MATCHED_HEADER, - LFT_ICAP_LAST_MATCHED_HEADER_ELEM, - LFT_ICAP_LAST_MATCHED_ALL_HEADERS, LFT_ICAP_ADDR, LFT_ICAP_SERV_NAME, === modified file 'src/log/access_log.cc' --- src/log/access_log.cc 2010-12-12 05:30:58 +0000 +++ src/log/access_log.cc 2011-02-18 23:58:13 +0000 @@ -311,18 +311,17 @@ LogfileStatus = LOG_ENABLE; -#if USE_ADAPTATION || ICAP_CLIENT +#if USE_ADAPTATION for (logformat_token * curr_token = (log->logFormat?log->logFormat->format:NULL); curr_token; curr_token = curr_token->next) { -#if USE_ADAPTATION if (curr_token->type == LTF_ADAPTATION_SUM_XACT_TIMES || - curr_token->type == LTF_ADAPTATION_ALL_XACT_TIMES) { + curr_token->type == LTF_ADAPTATION_ALL_XACT_TIMES || + curr_token->type == LFT_ADAPTATION_LAST_HEADER || + curr_token->type == LFT_ADAPTATION_LAST_HEADER_ELEM || + curr_token->type == LFT_ADAPTATION_LAST_ALL_HEADERS) { alLogformatHasAdaptToken = true; } -#endif #if ICAP_CLIENT - if (curr_token->type == LFT_ICAP_LAST_MATCHED_HEADER || - curr_token->type == LFT_ICAP_LAST_MATCHED_HEADER_ELEM || - curr_token->type == LFT_ICAP_LAST_MATCHED_ALL_HEADERS) { + if (curr_token->type == LFT_ICAP_TOTAL_TIME) { alLogformatHasIcapToken = true; } #endif @@ -573,7 +572,7 @@ safe_free(aLogEntry->headers.request); #if ICAP_CLIENT - safe_free(aLogEntry->headers.icap); + safe_free(aLogEntry->headers.adapt_last); #endif safe_free(aLogEntry->headers.reply); # Begin bundle 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