Felix Meschberger <felix.meschberger@day.com>
Mark Bergsma <mark@nedworks.org>
Tim Starling <tstarling@wikimedia.org>
+ Tsantilas Christos <chtsanti@users.sourceforge.net>
Duane Wessels <wessels@squid-cache.org>
/*
- * $Id: HttpReply.cc,v 1.89 2006/06/07 22:39:33 hno Exp $
+ * $Id: HttpReply.cc,v 1.90 2006/10/31 23:30:56 wessels Exp $
*
* DEBUG: section 58 HTTP Reply (Response)
* AUTHOR: Alex Rousskov
httpBodyInit(&body);
hdrCacheInit();
httpStatusLineInit(&sline);
+ pstate = psReadyToParseStartLine;
do_clean = true;
}
/*
- * $Id: HttpRequest.cc,v 1.68 2006/09/26 13:30:09 adrian Exp $
+ * $Id: HttpRequest.cc,v 1.69 2006/10/31 23:30:56 wessels Exp $
*
* DEBUG: section 73 HTTP Request
* AUTHOR: Duane Wessels
extacl_user = null_string;
extacl_passwd = null_string;
extacl_log = null_string;
+ pstate = psReadyToParseStartLine;
}
void
size_t crlfEnd = 0;
if (findCrlf(crlfBeg, crlfEnd)) {
- debugs(99,5, "found chunk-size end: " << crlfBeg << "-" << crlfEnd);
+ debugs(93,7, "found chunk-size end: " << crlfBeg << "-" << crlfEnd);
int size = -1;
const char *p = 0;
theIn->consume(crlfEnd);
theChunkSize = theLeftBodySize = size;
- debugs(99,5, "found chunk: " << theChunkSize);
+ debugs(93,7, "found chunk: " << theChunkSize);
theStep = theChunkSize == 0 ? psTrailer : psChunkBody;
return;
}
#include "squid.h"
#include "client_side_request.h"
#include "ClientRequestContext.h"
-#include "MsgPipe.h"
#include "MsgPipeData.h"
-#include "MsgPipeSource.h"
-#include "MsgPipeSink.h"
#include "HttpRequest.h"
#include "ICAPClientReqmodPrecache.h"
#include "ICAPServiceRep.h"
CBDATA_CLASS_INIT(ICAPClientReqmodPrecache);
-ICAPClientReqmodPrecache::ICAPClientReqmodPrecache(ICAPServiceRep::Pointer aService): service(aService), http(NULL), virgin(NULL), adapted(NULL)
+ICAPClientReqmodPrecache::ICAPClientReqmodPrecache(ICAPServiceRep::Pointer aService):
+ ICAPClientVector(aService, "ICAPClientReqmodPrecache"), http(NULL)
{
- debug(93,3)("ICAPClientReqmodPrecache constructed, this=%p\n", this);
-}
-
-ICAPClientReqmodPrecache::~ICAPClientReqmodPrecache()
-{
- stop(notifyNone);
- cbdataReferenceDone(http);
- debug(93,3)("ICAPClientReqmodPrecache destructed, this=%p\n", this);
-
- if (virgin != NULL)
- freeVirgin();
-
- if (adapted != NULL) {
- /*
- * adapted->sink is equal to this. Remove the pointer since
- * we are deleting this.
- */
-
- if (adapted->sink)
- adapted->sink = NULL;
-
- freeAdapted();
- }
}
void ICAPClientReqmodPrecache::startReqMod(ClientHttpRequest *aHttp, HttpRequest *request)
{
- debug(93,3)("ICAPClientReqmodPrecache::startReqMod() called\n");
http = cbdataReference(aHttp);
-
- virgin = new MsgPipe("virgin"); // this is the place to create a refcount ptr
- virgin->source = this;
- virgin->data = new MsgPipeData;
- virgin->data->cause = NULL;
- virgin->data->setHeader(request);
- virgin->data->body = new MemBuf;
- virgin->data->body->init(ICAP::MsgPipeBufSizeMin, ICAP::MsgPipeBufSizeMax);
-
- adapted = new MsgPipe("adapted");
- adapted->sink = this;
-
- ICAPInitXaction(service, virgin, adapted);
-
- virgin->sendSourceStart(); // we may have virgin data to provide
- adapted->sendSinkNeed(); // we want adapted response, eventially
-}
-
-void ICAPClientReqmodPrecache::sendMoreData(StoreIOBuffer buf)
-{
- debug(93,3)("ICAPClientReqmodPrecache::sendMoreData() called\n");
- //buf.dump();
- /*
- * The caller is responsible for not giving us more data
- * than will fit in body MemBuf. Caller should use
- * potentialSpaceSize() to find out how much we can hold.
- */
- virgin->data->body->append(buf.data, buf.length);
- virgin->sendSourceProgress();
+ startMod(http, NULL, request);
}
-int
-ICAPClientReqmodPrecache::potentialSpaceSize()
-{
- if (virgin == NULL)
- return 0;
-
- return (int) virgin->data->body->potentialSpaceSize();
-}
-
-// ClientHttpRequest says we have the entire HTTP message
-void ICAPClientReqmodPrecache::doneSending()
-{
- debug(93,3)("ICAPClientReqmodPrecache::doneSending() called\n");
-
- virgin->sendSourceFinish();
-}
-
-// ClientHttpRequest tells us to abort
-void ICAPClientReqmodPrecache::ownerAbort()
-{
- debug(93,3)("ICAPClientReqmodPrecache::ownerAbort() called\n");
- stop(notifyIcap);
-}
-
-// ICAP client needs more virgin response data
-void ICAPClientReqmodPrecache::noteSinkNeed(MsgPipe *p)
-{
- debug(93,3)("ICAPClientReqmodPrecache::noteSinkNeed() called\n");
-
- if (virgin->data->body->potentialSpaceSize())
- http->icapSpaceAvailable();
-}
-
-// ICAP client aborting
-void ICAPClientReqmodPrecache::noteSinkAbort(MsgPipe *p)
-{
- debug(93,3)("ICAPClientReqmodPrecache::noteSinkAbort() called\n");
- stop(notifyOwner);
+void ICAPClientReqmodPrecache::tellSpaceAvailable() {
+ http->icapSpaceAvailable();
}
// ICAP client starts sending adapted response
HttpRequest *req = dynamic_cast<HttpRequest*>(adapted->data->header);
if (req) {
- debugs(32,3,HERE << "notifying body_reader, contentSize() = " << p->data->body->contentSize());
+ debugs(93,3,HERE << "notifying body_reader, contentSize() = " << p->data->body->contentSize());
req->body_reader->notify(p->data->body->contentSize());
} else {
http->takeAdaptedBody(adapted->data->body);
}
}
-// ICAP client is done sending adapted response
-void ICAPClientReqmodPrecache::noteSourceFinish(MsgPipe *p)
+void ICAPClientReqmodPrecache::tellDoneAdapting()
{
- debug(93,3)("ICAPClientReqmodPrecache::noteSourceFinish() called\n");
+ debug(93,3)("ICAPClientReqmodPrecache::tellDoneAdapting() called\n");
//tell ClientHttpRequest that we expect no more response data
- http->doneAdapting();
+ http->doneAdapting(); // does not delete us (yet?)
stop(notifyNone);
+ // we should be eventually deleted by owner in ~ClientHttpRequest()
}
-// ICAP client is aborting
-void ICAPClientReqmodPrecache::noteSourceAbort(MsgPipe *p)
+void ICAPClientReqmodPrecache::tellAbortAdapting()
{
- debug(93,3)("ICAPClientReqmodPrecache::noteSourceAbort() called\n");
- stop(notifyOwner);
+ debug(93,3)("ICAPClientReqmodPrecache::tellAbortAdapting() called\n");
+ // tell ClientHttpRequest that we are aborting ICAP processing prematurely
+ http->abortAdapting();
}
// internal cleanup
void ICAPClientReqmodPrecache::stop(Notify notify)
{
- if (virgin != NULL) {
- if (notify == notifyIcap)
- virgin->sendSourceAbort();
- else
- virgin->source = NULL;
-
- freeVirgin();
- }
-
-#if DONT_FREE_ADAPTED
/*
* NOTE: We do not clean up "adapted->sink" here because it may
* have an HTTP message body that needs to stay around a little
* while longer so that the HTTP server-side can forward it on.
*/
- if (adapted != NULL) {
- if (notify == notifyIcap)
- adapted->sendSinkAbort();
- else
- adapted->sink = NULL;
-
- freeAdapted();
- }
-
-#endif
-
- if (http) {
- if (notify == notifyOwner)
- // tell ClientHttpRequest that we are aborting prematurely
- http->abortAdapting();
- cbdataReferenceDone(http);
+ // XXX: who will clean up the "adapted->sink" then? Does it happen
+ // when the owner deletes us? Is that why we are deleted when the
+ // owner is destroyed and not when ICAP adaptation is done, like
+ // in http.cc case?
- // http is now NULL, will not call it any more
- }
-}
-
-void ICAPClientReqmodPrecache::freeVirgin()
-{
- // virgin->data->cause should be NULL;
- virgin = NULL; // refcounted
-}
+ // XXX: "adapted->sink" does not really have an "HTTP message body",
+ // In fact, it simply points to "this". Should the above comment
+ // refer to adapted and adapted->data->body?
-void ICAPClientReqmodPrecache::freeAdapted()
-{
- adapted = NULL; // refcounted
+ ICAPClientVector::clean(notify, false);
}
/*
assert(icap->adapted->data != NULL);
MemBuf *bodybuf = icap->adapted->data->body;
assert(bodybuf != NULL);
- debugs(32,3,HERE << "readBody requested size " << size);
- debugs(32,3,HERE << "readBody bodybuf size " << bodybuf->contentSize());
+ debugs(93,3,HERE << "readBody requested size " << size);
+ debugs(93,3,HERE << "readBody bodybuf size " << bodybuf->contentSize());
if ((mb_size_t) size > bodybuf->contentSize())
size = bodybuf->contentSize();
- debugs(32,3,HERE << "readBody actual size " << size);
+ debugs(93,3,HERE << "readBody actual size " << size);
assert(size);
ICAPClientReqmodPrecache::abortBody(void *data, size_t remaining)
{
if (remaining >= 0) {
- debugs(0,0,HERE << "ICAPClientReqmodPrecache::abortBody size " << remaining);
+ debugs(93,1,HERE << "ICAPClientReqmodPrecache::abortBody size " << remaining);
// more?
}
void
ICAPClientReqmodPrecache::kickBody(void *data)
{
- debugs(32,3,HERE << "ICAPClientReqmodPrecache::kickBody");
+ debugs(93,3,HERE << "ICAPClientReqmodPrecache::kickBody");
ICAPClientReqmodPrecache *icap = static_cast<ICAPClientReqmodPrecache *>(data);
assert(icap->adapted != NULL);
icap->adapted->sendSinkNeed();
/*
- * $Id: ICAPClientReqmodPrecache.h,v 1.3 2006/04/27 19:27:37 wessels Exp $
+ * $Id: ICAPClientReqmodPrecache.h,v 1.4 2006/10/31 23:30:58 wessels Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
*
*/
-#ifndef SQUID_ICAPCLIENTSIDEHOOK_H
-#define SQUID_ICAPCLIENTSIDEHOOK_H
+#ifndef SQUID_ICAPCLIENTREQMODPRECACHE_H
+#define SQUID_ICAPCLIENTREQMODPRECACHE_H
-#include "MsgPipe.h"
-#include "MsgPipeSource.h"
-#include "MsgPipeSink.h"
+#include "ICAPClientVector.h"
-/* The ICAP ClientReqmodPrecache implements message pipe sink and source interfaces. It
- * helps client-side to marshall the incoming/virgin HTTP message (being
- * recieved from the HTTP client) to Squid's ICAP client module, using the
- * MsgPipe interface. The same interface is used to get the adapted HTTP
- * message back from the ICAP client. client-side is the "owner" of the
- * ICAPClientReqmodPrecache.
+/*
+ * ICAPClientReqmodPrecache implements the ICAP client-side pre-cache
+ * vectoring point using ICAPClientVector as a parent.
+ * ClientHttpRequest is the Owner of this vectoring point.
*/
-class HttpRequest;
-
class ClientRequestContext;
-class ICAPClientReqmodPrecache: public MsgPipeSource, public MsgPipeSink
+class ICAPClientReqmodPrecache: public ICAPClientVector
{
public:
ICAPClientReqmodPrecache(ICAPServiceRep::Pointer);
- virtual ~ICAPClientReqmodPrecache();
// synchronous calls called by ClientHttpRequest
void startReqMod(ClientHttpRequest *, HttpRequest *);
- void sendMoreData(StoreIOBuffer buf);
- void doneSending();
- void ownerAbort();
- int potentialSpaceSize(); /* how much data can we accept? */
// pipe source methods; called by ICAP while receiving the virgin message
- virtual void noteSinkNeed(MsgPipe *p);
- virtual void noteSinkAbort(MsgPipe *p);
+
// pipe sink methods; called by ICAP while sending the adapted message
virtual void noteSourceStart(MsgPipe *p);
virtual void noteSourceProgress(MsgPipe *p);
- virtual void noteSourceFinish(MsgPipe *p);
- virtual void noteSourceAbort(MsgPipe *p);
+
+protected:
+ // used by ICAPClientVector because it does not know Owner type
+ virtual void tellSpaceAvailable();
+ virtual void tellDoneAdapting();
+ virtual void tellAbortAdapting();
+ virtual void stop(Notify notify);
public:
- ICAPServiceRep::Pointer service;
ClientHttpRequest *http;
- MsgPipe::Pointer virgin;
- MsgPipe::Pointer adapted;
BodyReader::Pointer body_reader;
private:
- typedef enum { notifyNone, notifyOwner, notifyIcap } Notify;
- void stop(Notify notify);
- void freeVirgin();
- void freeAdapted();
- CBDATA_CLASS2(ICAPClientReqmodPrecache);
-
// Hooks to BodyReader so HttpStateData can get the
// adapted request body
static BodyReadFunc readBody;
static BodyAbortFunc abortBody;
static BodyKickFunc kickBody;
+
+ CBDATA_CLASS2(ICAPClientReqmodPrecache);
};
#endif /* SQUID_ICAPCLIENTSIDEHOOK_H */
#include "squid.h"
#include "http.h"
-#include "MsgPipe.h"
#include "MsgPipeData.h"
-#include "MsgPipeSource.h"
-#include "MsgPipeSink.h"
#include "HttpRequest.h"
#include "HttpReply.h"
#include "ICAPClientRespmodPrecache.h"
CBDATA_CLASS_INIT(ICAPClientRespmodPrecache);
-ICAPClientRespmodPrecache::ICAPClientRespmodPrecache(ICAPServiceRep::Pointer aService): service(aService), serverState(NULL), virgin(NULL), adapted(NULL)
+ICAPClientRespmodPrecache::ICAPClientRespmodPrecache(ICAPServiceRep::Pointer aService):
+ ICAPClientVector(aService, "ICAPClientRespmodPrecache"), serverState(NULL)
{
- debug(93,5)("ICAPClientRespmodPrecache constructed, this=%p\n", this);
}
-ICAPClientRespmodPrecache::~ICAPClientRespmodPrecache()
+void ICAPClientRespmodPrecache::startRespMod(ServerStateData *aServerState, HttpRequest *request, HttpReply *reply)
{
- stop(notifyNone);
- cbdataReferenceDone(serverState);
- debug(93,5)("ICAPClientRespmodPrecache destructed, this=%p\n", this);
-
- if (virgin != NULL)
- freeVirgin();
-
- if (adapted != NULL)
- freeAdapted();
-
- service = NULL;
-}
-
-void ICAPClientRespmodPrecache::startRespMod(ServerStateData *anServerState, HttpRequest *request, HttpReply *reply)
-{
- serverState = cbdataReference(anServerState);
-
- virgin = new MsgPipe("virgin"); // this is the place to create a refcount ptr
- virgin->source = this;
- virgin->data = new MsgPipeData;
- virgin->data->setCause(request);
- virgin->data->setHeader(reply);
- virgin->data->body = new MemBuf;
- virgin->data->body->init(ICAP::MsgPipeBufSizeMin, ICAP::MsgPipeBufSizeMax);
-
- adapted = new MsgPipe("adapted");
- adapted->sink = this;
-#if ICAP_ANCHOR_LOOPBACK
-
- adapted->data = new MsgPipeData;
- adapted->data->setCause(request); // should not hurt
-#else
-
- ICAPInitXaction(service, virgin, adapted);
-#endif
-
- virgin->sendSourceStart(); // we may have virgin data to provide
- adapted->sendSinkNeed(); // we want adapted response, eventially
-}
-
-void ICAPClientRespmodPrecache::sendMoreData(StoreIOBuffer buf)
-{
- debug(93,5)("ICAPClientRespmodPrecache::sendMoreData() called\n");
- //debugs(93,0,HERE << "appending " << buf.length << " bytes");
- //debugs(93,0,HERE << "body.contentSize = " << virgin->data->body->contentSize());
- //buf.dump();
- /*
- * The caller is responsible for not giving us more data
- * than will fit in body MemBuf. Caller should use
- * potentialSpaceSize() to find out how much we can hold.
- */
- virgin->data->body->append(buf.data, buf.length);
- virgin->sendSourceProgress();
-}
-
-int
-ICAPClientRespmodPrecache::potentialSpaceSize()
-{
- if (virgin == NULL)
- return 0;
-
- return (int) virgin->data->body->potentialSpaceSize();
-}
-
-// ServerStateData says we have the entire HTTP message
-void ICAPClientRespmodPrecache::doneSending()
-{
- debug(93,5)("ICAPClientRespmodPrecache::doneSending() called\n");
-
-#if ICAP_ANCHOR_LOOPBACK
- /* simple assignments are not the right way to do this */
- adapted->data->setHeader(virgin->data->header);
- adapted->data->body = virgin->data->body;
- noteSourceFinish(adapted);
- return;
-#else
-
- virgin->sendSourceFinish();
-#endif
-}
-
-// ServerStateData tells us to abort
-void ICAPClientRespmodPrecache::ownerAbort()
-{
- debug(93,5)("ICAPClientRespmodPrecache::ownerAbort() called\n");
- stop(notifyIcap);
-}
-
-// ICAP client needs more virgin response data
-void ICAPClientRespmodPrecache::noteSinkNeed(MsgPipe *p)
-{
- debug(93,5)("ICAPClientRespmodPrecache::noteSinkNeed() called\n");
-
- if (virgin->data->body->potentialSpaceSize())
- serverState->icapSpaceAvailable();
-}
-
-// ICAP client aborting
-void ICAPClientRespmodPrecache::noteSinkAbort(MsgPipe *p)
-{
- debug(93,5)("ICAPClientRespmodPrecache::noteSinkAbort() called\n");
- stop(notifyOwner);
+ serverState = cbdataReference(aServerState);
+ startMod(serverState, request, reply);
}
// ICAP client starts sending adapted response
// ICAP client has received new HTTP headers (if any) at this point
void ICAPClientRespmodPrecache::noteSourceStart(MsgPipe *p)
{
- debugs(93,5, HERE << "ICAPClientRespmodPrecache::noteSourceStart() called");
+ debugs(93,3, HERE << "ICAPClientRespmodPrecache::noteSourceStart() called");
HttpReply *reply = dynamic_cast<HttpReply*>(adapted->data->header);
/*
- * The ICAP reply MUST have a new HTTP reply header, or else
- * it is an invalid ICAP message. Invalid ICAP messages should
- * be handled prior to this point.
+ * The ICAP reply MUST have a new HTTP reply header, or else
+ * it is an invalid ICAP message. Invalid ICAP messages should
+ * be handled prior to this point.
*/
assert(reply); // check that ICAP xaction created the right object
assert(reply == adapted->data->header);
ssize_t dummy;
bool expect_body = reply->expectingBody(virgin->data->cause->method, dummy);
- serverState->takeAdaptedHeaders(reply);
+ if (!serverState->takeAdaptedHeaders(reply)) // deletes us
+ return;
if (expect_body)
noteSourceProgress(p);
// ICAP client sends more data
void ICAPClientRespmodPrecache::noteSourceProgress(MsgPipe *p)
{
- debug(93,5)("ICAPClientRespmodPrecache::noteSourceProgress() called\n");
+ debug(93,3)("ICAPClientRespmodPrecache::noteSourceProgress() called\n");
//tell ServerStateData to store a fresh portion of the adapted response
assert(serverState);
if (p->data->body->hasContent()) {
- serverState->takeAdaptedBody(p->data->body);
- }
-}
+ if (!serverState->takeAdaptedBody(p->data->body))
+ return;
-// ICAP client is done sending adapted response
-void ICAPClientRespmodPrecache::noteSourceFinish(MsgPipe *p)
-{
- debug(93,5)("ICAPClientRespmodPrecache::noteSourceFinish() called\n");
- //tell ServerStateData that we expect no more response data
- serverState->doneAdapting();
- stop(notifyNone);
+ // HttpStateData::takeAdaptedBody does not detect when we have enough,
+ // so we always notify source that there more buffer space is available
+ if (p->data->body->hasPotentialSpace())
+ adapted->sendSinkNeed();
+ }
}
-// ICAP client is aborting
-void ICAPClientRespmodPrecache::noteSourceAbort(MsgPipe *p)
+void
+ICAPClientRespmodPrecache::tellSpaceAvailable()
{
- debug(93,5)("ICAPClientRespmodPrecache::noteSourceAbort() called\n");
- stop(notifyOwner);
+ serverState->icapSpaceAvailable();
}
-// internal cleanup
-void ICAPClientRespmodPrecache::stop(Notify notify)
+void
+ICAPClientRespmodPrecache::tellDoneAdapting()
{
- if (virgin != NULL) {
- if (notify == notifyIcap)
- virgin->sendSourceAbort();
- else
- virgin->source = NULL;
-
- freeVirgin();
- }
-
- if (adapted != NULL) {
- if (notify == notifyIcap)
- adapted->sendSinkAbort();
- else
- adapted->sink = NULL;
-
- freeAdapted();
- }
-
- if (serverState) {
- if (notify == notifyOwner)
- // tell ServerStateData that we are aborting prematurely
- serverState->abortAdapting();
-
- cbdataReferenceDone(serverState);
-
- // serverState is now NULL, will not call it any more
- }
+ serverState->finishAdapting(); // deletes us
}
-void ICAPClientRespmodPrecache::freeVirgin()
+void
+ICAPClientRespmodPrecache::tellAbortAdapting()
{
- virgin = NULL; // refcounted
+ debug(93,3)("ICAPClientReqmodPrecache::tellAbortAdapting() called\n");
+ // tell ClientHttpRequest that we are aborting ICAP processing prematurely
+ serverState->abortAdapting(); // deletes us
}
-void ICAPClientRespmodPrecache::freeAdapted()
-{
- adapted = NULL; // refcounted
-}
/*
- * $Id: ICAPClientRespmodPrecache.h,v 1.3 2006/01/25 17:41:23 wessels Exp $
+ * $Id: ICAPClientRespmodPrecache.h,v 1.4 2006/10/31 23:30:58 wessels Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
*
*/
-#ifndef SQUID_ICAPANCHOR_H
-#define SQUID_ICAPANCHOR_H
+#ifndef SQUID_ICAPCLIENTRESPMODPRECACHE_H
+#define SQUID_ICAPCLIENTRESPMODPRECACHE_H
-#include "MsgPipe.h"
-#include "MsgPipeSource.h"
-#include "MsgPipeSink.h"
-#include "ICAPServiceRep.h"
+#include "ICAPClientVector.h"
-/* The ICAP Anchor implements message pipe sink and source interfaces. It
- * helps ServerStateData to marshall the incoming/virgin HTTP message (being
- * recieved from the HTTP server) to Squid's ICAP client module, using the
- * MsgPipe interface. The same interface is used to get the adapted HTTP
- * message back from the ICAP client. ServerStateData is the "owner" of the
- * ICAPClientRespmodPrecache.
+/*
+ * ICAPClientRespmodPrecache implements the server-side pre-cache ICAP
+ * vectoring point using ICAPClientVector as a parent.
+ * ServerStateData is the Owner of this vectoring point.
*/
-class HttpRequest;
-
-class HttpReply;
+class ServerStateData;
-class ICAPClientRespmodPrecache: public MsgPipeSource, public MsgPipeSink
+class ICAPClientRespmodPrecache: public ICAPClientVector
{
public:
ICAPClientRespmodPrecache(ICAPServiceRep::Pointer);
- virtual ~ICAPClientRespmodPrecache();
// synchronous calls called by ServerStateData
void startRespMod(ServerStateData *anServerState, HttpRequest *request, HttpReply *reply);
- void sendMoreData(StoreIOBuffer buf);
- void doneSending();
- void ownerAbort();
- int potentialSpaceSize(); /* how much data can we accept? */
// pipe source methods; called by ICAP while receiving the virgin message
- virtual void noteSinkNeed(MsgPipe *p);
- virtual void noteSinkAbort(MsgPipe *p);
// pipe sink methods; called by ICAP while sending the adapted message
virtual void noteSourceStart(MsgPipe *p);
virtual void noteSourceProgress(MsgPipe *p);
- virtual void noteSourceFinish(MsgPipe *p);
- virtual void noteSourceAbort(MsgPipe *p);
+
+protected:
+ virtual void tellSpaceAvailable();
+ virtual void tellDoneAdapting(); // deletes us
+ virtual void tellAbortAdapting(); // deletes us
public:
- ICAPServiceRep::Pointer service;
ServerStateData *serverState;
- MsgPipe::Pointer virgin;
- MsgPipe::Pointer adapted;
private:
- typedef enum { notifyNone, notifyOwner, notifyIcap } Notify;
- void stop(Notify notify);
- void freeVirgin();
- void freeAdapted();
CBDATA_CLASS2(ICAPClientRespmodPrecache);
};
-#endif /* SQUID_ICAPANCHOR_H */
+#endif /* SQUID_ICAPCLIENTRESPMODPRECACHE_H */
--- /dev/null
+#include "squid.h"
+#include "MsgPipe.h"
+#include "MsgPipeData.h"
+#include "MsgPipeSource.h"
+#include "MsgPipeSink.h"
+#include "HttpRequest.h"
+#include "ICAPClientVector.h"
+#include "ICAPClient.h"
+
+ICAPClientVector::ICAPClientVector(ICAPServiceRep::Pointer aService, const char *aPoint):
+ theOwner(0), vPoint(aPoint),
+ service(aService), virgin(NULL), adapted(NULL)
+{
+ debug(93,3)("%s constructed, this=%p\n", vPoint, this);
+}
+
+ICAPClientVector::~ICAPClientVector()
+{
+ stop(notifyNone);
+ debug(93,3)("%s destructed, this=%p\n", vPoint, this);
+}
+
+void ICAPClientVector::startMod(void *anOwner, HttpRequest *cause, HttpMsg *header)
+{
+ debug(93,5)("%s starting, this=%p\n", vPoint, this);
+
+ theOwner = anOwner;
+
+ virgin = new MsgPipe("virgin"); // this is the place to create a refcount ptr
+ virgin->source = this;
+ virgin->data = new MsgPipeData;
+ virgin->data->setCause(cause);
+ virgin->data->setHeader(header);
+ virgin->data->body = new MemBuf;
+ virgin->data->body->init(ICAP::MsgPipeBufSizeMin, ICAP::MsgPipeBufSizeMax);
+
+ adapted = new MsgPipe("adapted");
+ adapted->sink = this;
+
+#if ICAP_ANCHOR_LOOPBACK
+ adapted->data = new MsgPipeData;
+ adapted->data->setCause(request); // should not hurt
+#else
+ ICAPInitXaction(service, virgin, adapted);
+#endif
+
+ virgin->sendSourceStart(); // we may have virgin data to provide
+ adapted->sendSinkNeed(); // we want adapted response, eventially
+}
+
+void ICAPClientVector::sendMoreData(StoreIOBuffer buf)
+{
+ debug(93,7)("%s::sendMoreData(%p)\n", vPoint, this);
+ //debugs(93,0,HERE << "appending " << buf.length << " bytes");
+ //debugs(93,0,HERE << "body.contentSize = " << virgin->data->body->contentSize());
+ //buf.dump();
+ /*
+ * The caller is responsible for not giving us more data
+ * than will fit in body MemBuf. Caller should use
+ * potentialSpaceSize() to find out how much we can hold.
+ */
+ virgin->data->body->append(buf.data, buf.length);
+ virgin->sendSourceProgress();
+}
+
+int
+ICAPClientVector::potentialSpaceSize()
+{
+ if (virgin == NULL)
+ return 0;
+
+ return (int) virgin->data->body->potentialSpaceSize();
+}
+
+// Owner says we have the entire HTTP message
+void ICAPClientVector::doneSending()
+{
+ debug(93,3)("%s::doneSending(%p)\n", vPoint, this);
+
+#if ICAP_ANCHOR_LOOPBACK
+ /* simple assignments are not the right way to do this */
+ adapted->data->setHeader(virgin->data->header);
+ adapted->data->body = virgin->data->body;
+ noteSourceFinish(adapted);
+ // checkDoneAdapting() does not support loopback mode
+ return;
+#else
+ virgin->sendSourceFinish();
+ checkDoneAdapting(); // may call the owner back, unfortunately
+#endif
+}
+
+// Owner tells us to abort
+void ICAPClientVector::ownerAbort()
+{
+ debug(93,3)("%s::ownerAbort(%p)\n", vPoint, this);
+ stop(notifyIcap);
+}
+
+// ICAP client needs more virgin response data
+void ICAPClientVector::noteSinkNeed(MsgPipe *p)
+{
+ debug(93,3)("%s::noteSinkNeed(%p)\n", vPoint, this);
+
+ if (virgin->data->body->potentialSpaceSize())
+ tellSpaceAvailable();
+}
+
+// ICAP client aborting
+void ICAPClientVector::noteSinkAbort(MsgPipe *p)
+{
+ debug(93,3)("%s::noteSinkAbort(%p)\n", vPoint, this);
+ stop(notifyOwner); // deletes us
+}
+
+// ICAP client is done sending adapted response
+void ICAPClientVector::noteSourceFinish(MsgPipe *p)
+{
+ debug(93,3)("%s::noteSourceFinish(%p)\n", vPoint, this);
+ checkDoneAdapting(); // may delete us
+}
+
+void ICAPClientVector::checkDoneAdapting() {
+ debug(93,5)("%s::checkDoneAdapting(%p): %d & %d\n", vPoint, this,
+ (int)!virgin->source, (int)!adapted->source);
+ // done if we are not sending and are not receiving
+ if (!virgin->source && !adapted->source)
+ tellDoneAdapting(); // deletes us
+}
+
+// ICAP client is aborting
+void ICAPClientVector::noteSourceAbort(MsgPipe *p)
+{
+ debug(93,3)("%s::noteSourceAbort(%p)\n", vPoint, this);
+ stop(notifyOwner); // deletes us
+}
+
+void ICAPClientVector::stop(Notify notify)
+{
+ debug(93,3)("%s::stop(%p, %d)\n", vPoint, this, (int)notify);
+ clean(notify, true);
+}
+
+void ICAPClientVector::clean(Notify notify, bool cleanAdapted)
+{
+ if (virgin != NULL) {
+ if (notify == notifyIcap)
+ virgin->sendSourceAbort();
+ else
+ virgin->source = NULL;
+ virgin = NULL; // refcounted
+ }
+
+ if (cleanAdapted && adapted != NULL) {
+ if (notify == notifyIcap)
+ adapted->sendSinkAbort();
+ else
+ adapted->sink = NULL;
+ adapted = NULL; // refcounted
+ }
+
+ service = NULL;
+
+ if (theOwner) {
+ if (notify == notifyOwner)
+ tellAbortAdapting(); // deletes us
+ else
+ cbdataReferenceDone(theOwner);
+ }
+
+ // not safe to do anything here because we may have been deleted.
+}
--- /dev/null
+
+/*
+ * $Id: ICAPClientVector.h,v 1.1 2006/10/31 23:30:58 wessels Exp $
+ *
+ *
+ * SQUID Web Proxy Cache http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ * Squid is the result of efforts by numerous individuals from
+ * the Internet community; see the CONTRIBUTORS file for full
+ * details. Many organizations have provided support for Squid's
+ * development; see the SPONSORS file for full details. Squid is
+ * Copyrighted (C) 2001 by the Regents of the University of
+ * California; see the COPYRIGHT file for full details. Squid
+ * incorporates software developed and/or copyrighted by other
+ * sources; see the CREDITS file for full details.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ *
+ */
+
+#ifndef SQUID_ICAPVECTOR_H
+#define SQUID_ICAPVECTOR_H
+
+#include "MsgPipe.h"
+#include "MsgPipeSource.h"
+#include "MsgPipeSink.h"
+#include "ICAPServiceRep.h"
+
+/*
+ * The ICAP Vector helps its Owner to talk to the ICAP transaction, which
+ * implements asynchronous communication with the ICAP server. The Owner
+ * is either the HTTP client side (ClientHttpRequest) or the HTTP server
+ * side (ServerStateData). The Vector marshals the incoming/virgin HTTP
+ * message to the ICAP transaction, via the MsgPipe interface. The same
+ * interface is used to get the adapted HTTP message back.
+ *
+ * ICAPClientReqmodPrecache and ICAPClientRespmodPrecache classes use
+ * ICAPVector as a base and cover specifics of their vectoring point.
+ */
+
+class ICAPClientVector: public MsgPipeSource, public MsgPipeSink
+{
+
+public:
+ ICAPClientVector(ICAPServiceRep::Pointer, const char *aPoint);
+ virtual ~ICAPClientVector();
+
+ // synchronous calls called by Owner
+ void sendMoreData(StoreIOBuffer buf);
+ void doneSending();
+ void ownerAbort();
+ int potentialSpaceSize(); /* how much data can we accept? */
+
+ // pipe source methods; called by ICAP while receiving the virgin message
+ virtual void noteSinkNeed(MsgPipe *p);
+ virtual void noteSinkAbort(MsgPipe *p);
+
+ // pipe sink methods; called by ICAP while sending the adapted message
+ virtual void noteSourceStart(MsgPipe *p) = 0;
+ virtual void noteSourceProgress(MsgPipe *p) = 0;
+ virtual void noteSourceFinish(MsgPipe *p);
+ virtual void noteSourceAbort(MsgPipe *p);
+
+protected:
+ typedef enum { notifyNone, notifyOwner, notifyIcap } Notify;
+
+ // implemented by kids because we do not have a common Owner parent
+ virtual void tellSpaceAvailable() = 0;
+ virtual void tellDoneAdapting() = 0; // may delete us
+ virtual void tellAbortAdapting() = 0; // may delete us
+ virtual void stop(Notify notify); // may delete us
+
+ void startMod(void *anOwner, HttpRequest *cause, HttpMsg *header);
+ void clean(Notify notify, bool cleanAdapted = true);
+
+private:
+ void checkDoneAdapting();
+
+public:
+ void *theOwner;
+ const char *vPoint; // unmanaged vectoring point name for debugging
+
+ ICAPServiceRep::Pointer service;
+ MsgPipe::Pointer virgin;
+ MsgPipe::Pointer adapted;
+};
+
+#endif /* SQUID_ICAPVECTOR_H */
/*
- * $Id: ICAPConfig.cc,v 1.11 2006/05/11 23:53:13 wessels Exp $
+ * $Id: ICAPConfig.cc,v 1.12 2006/10/31 23:30:58 wessels Exp $
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
* ----------------------------------------------------------
for (ci = TheICAPConfig.classes.begin(); ci != TheICAPConfig.classes.end(); ++ci) {
- ICAPClass *theClass = *ci;
-
- Vector<ICAPServiceRep::Pointer>::iterator si;
-
- for (si = theClass->services.begin(); si != theClass->services.end(); ++si) {
- ICAPServiceRep *theService = si->getRaw();
-
- if (method != theService->method)
- continue;
-
- if (point != theService->point)
- continue;
-
- debug(93,3)("ICAPAccessCheck::check: class '%s' has candidate service '%s'\n", theClass->key.buf(), theService->key.buf());
-
- candidateClasses += theClass->key;
-
- /*
- * Break here because we only need one matching service
- * to justify ACL-checking a class. We might use other
- * services belonging to the class if the first service
- * is unavailable, etc.
- */
- break;
-
+ /*
+ * We only find the first matching service because we only need
+ * one matching service to justify ACL-checking a class. We might
+ * use other services belonging to the class if the first service
+ * turns out to be unusable for some reason.
+ */
+ ICAPClass *c = *ci;
+ ICAPServiceRep::Pointer service = findBestService(c, false);
+ if (service.getRaw()) {
+ debug(93,3)("ICAPAccessCheck::check: class '%s' has candidate service '%s'\n", c->key.buf(), service->key.buf());
+ candidateClasses += c->key;
}
}
return;
}
- Vector<ICAPServiceRep::Pointer>::iterator i;
+ const ICAPServiceRep::Pointer service = findBestService(theClass, true);
+ if (!service)
+ callback(NULL, validated_cbdata);
+ else
+ callback(service, validated_cbdata);
+}
- for (i = theClass->services.begin(); i != theClass->services.end(); ++i) {
- ICAPServiceRep *theService = i->getRaw();
+ICAPServiceRep::Pointer
+ICAPAccessCheck::findBestService(ICAPClass *c, bool preferUp) {
- if (method != theService->method)
+ const char *what = preferUp ? "up " : "";
+ debugs(93,7,HERE << "looking for the first matching " <<
+ what << "service in class " << c->key);
+
+ ICAPServiceRep::Pointer secondBest;
+
+ Vector<ICAPServiceRep::Pointer>::iterator si;
+ for (si = c->services.begin(); si != c->services.end(); ++si) {
+ ICAPServiceRep::Pointer service = *si;
+
+ if (method != service->method)
continue;
- if (point != theService->point)
+ if (point != service->point)
continue;
- callback(*i, validated_cbdata);
+ // sending a message to a broken service is likely to cause errors
+ if (service->bypass && service->broken())
+ continue;
- return;
+ if (service->up()) {
+ // sending a message to a service that does not want it is useless
+ // note that we cannot check wantsUrl for service that is not "up"
+ // note that even essential services are skipped on unwanted URLs!
+ if (!service->wantsUrl(req->urlpath))
+ continue;
+ } else {
+ if (!secondBest)
+ secondBest = service;
+ if (preferUp) {
+ // the caller asked for an "up" service and we can bypass this one
+ if (service->bypass)
+ continue;
+ debugs(93,5,HERE << "cannot skip an essential down service");
+ what = "down-but-essential ";
+ }
+ }
+
+ debugs(93,5,HERE << "found first matching " <<
+ what << "service in class " << c->key <<
+ ": " << service->key);
+
+ return service;
+ }
+
+ if (secondBest.getRaw()) {
+ what = "down ";
+ debugs(93,5,HERE << "found first matching " <<
+ what << "service in class " << c->key <<
+ ": " << secondBest->key);
+ return secondBest;
}
- callback(NULL, callback_data);
+ debugs(93,5,HERE << "found no matching " <<
+ what << "services in class " << c->key);
+ return ICAPServiceRep::Pointer();
}
// ================================================================================ //
/*
- * $Id: ICAPConfig.h,v 1.9 2006/08/07 02:28:24 robertc Exp $
+ * $Id: ICAPConfig.h,v 1.10 2006/10/31 23:30:58 wessels Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
Vector<String> candidateClasses;
String matchedClass;
void do_callback();
+ ICAPServiceRep::Pointer findBestService(ICAPClass *c, bool preferUp);
public:
void check();
Must(state.serviceWaiting);
state.serviceWaiting = false;
- startWriting(); // will throw if service is not up
+
+ Must(service().up());
+
+ startWriting();
ICAPXaction_Exit();
}
void ICAPModXact::startWriting()
{
- Must(service().up());
-
state.writing = State::writingConnect;
openConnection();
// put nothing here as openConnection calls commConnectStart
virginWriteClaim.protectAll();
writeMore();
} else {
- stopWriting();
+ stopWriting(true);
}
}
case State::writingPaused: // waiting for the ICAP server response
- case State::writingDone: // nothing more to write
+ case State::writingReallyDone: // nothing more to write
+ return;
+
+ case State::writingAlmostDone: // was waiting for the last write
+ stopWriting(false);
return;
case State::writingPreview:
debugs(93, 7, "ICAPModXact wrote entire Preview body " << status());
if (preview.ieof())
- stopWriting();
+ stopWriting(true);
else
state.writing = State::writingPaused;
}
const size_t size = body->contentSize();
writeSomeBody("prime virgin body", size);
- if (state.doneReceiving) {
- debugs(98, 5, HERE << "state.doneReceiving is set");
- stopWriting();
+ if (state.doneReceiving && claimSize(virginWriteClaim) <= 0) {
+ debugs(93, 5, HERE << "state.doneReceiving is set and wrote all");
+ stopWriting(true);
}
}
void ICAPModXact::writeSomeBody(const char *label, size_t size)
{
- Must(!writer && !state.doneWriting());
+ Must(!writer && state.writing < state.writingAlmostDone);
debugs(93, 8, HERE << "will write up to " << size << " bytes of " <<
label);
writeBuf.init(); // note: we assume that last-chunk will fit
- const size_t writeableSize = claimSize(virginWriteClaim);
- const size_t chunkSize = XMIN(writeableSize, size);
+ const size_t writableSize = claimSize(virginWriteClaim);
+ const size_t chunkSize = XMIN(writableSize, size);
if (chunkSize) {
debugs(93, 7, HERE << "will write " << chunkSize <<
"-byte chunk of " << label);
} else {
- debugs(93, 7, "ICAPModXact has no writeable " << label << " content");
+ debugs(93, 7, "ICAPModXact has no writable " << label << " content");
}
moveRequestChunk(writeBuf, chunkSize);
void ICAPModXact::moveRequestChunk(MemBuf &buf, size_t chunkSize)
{
if (chunkSize > 0) {
- openChunk(buf, chunkSize);
+ openChunk(buf, chunkSize, false);
buf.append(claimContent(virginWriteClaim), chunkSize);
- closeChunk(buf, false);
+ closeChunk(buf);
virginWriteClaim.release(chunkSize);
virginConsume();
}
- if (state.writing == State::writingPreview)
- preview.wrote(chunkSize, state.doneReceiving); // even if wrote nothing
+ if (state.writing == State::writingPreview) {
+ // even if we are doneReceiving, we may not have written everything
+ const bool wroteEof = state.doneReceiving &&
+ claimSize(virginWriteClaim) <= 0;
+ preview.wrote(chunkSize, wroteEof); // even if wrote nothing
+ }
}
void ICAPModXact::addLastRequestChunk(MemBuf &buf)
{
- openChunk(buf, 0);
- closeChunk(buf, state.writing == State::writingPreview && preview.ieof());
+ const bool ieof = state.writing == State::writingPreview && preview.ieof();
+ openChunk(buf, 0, ieof);
+ closeChunk(buf);
}
-void ICAPModXact::openChunk(MemBuf &buf, size_t chunkSize)
+void ICAPModXact::openChunk(MemBuf &buf, size_t chunkSize, bool ieof)
{
- buf.Printf("%x\r\n", (int) chunkSize);
+ buf.Printf((ieof ? "%x; ieof\r\n" : "%x\r\n"), (int) chunkSize);
}
-void ICAPModXact::closeChunk(MemBuf &buf, bool ieof)
+void ICAPModXact::closeChunk(MemBuf &buf)
{
- if (ieof)
- buf.append("; ieof", 6);
-
buf.append(ICAP::crlf, 2); // chunk-terminating CRLF
}
writeMore();
}
-void ICAPModXact::stopWriting()
+// Called when we do not expect to call comm_write anymore.
+// We may have a pending write though.
+// If stopping nicely, we will just wait for that pending write, if any.
+void ICAPModXact::stopWriting(bool nicely)
{
- if (state.writing == State::writingDone)
+ if (state.writing == State::writingReallyDone)
return;
- debugs(93, 7, HERE << "will no longer write " << status());
+ if (writer) {
+ if (nicely) {
+ debugs(93, 7, HERE << "will wait for the last write " << status());
+ state.writing = State::writingAlmostDone; // may already be set
+ return;
+ }
+ debugs(93, 2, HERE << "will NOT wait for the last write " << status());
- state.writing = State::writingDone;
+ // Comm does not have an interface to clear the writer callback nicely,
+ // but without clearing the writer we cannot recycle the connection.
+ // We prevent connection reuse and hope that we can handle a callback
+ // call at any time. Somebody should either fix this code or add
+ // comm_remove_write_handler() to comm API.
+ reuseConnection = false;
+ }
+
+ debugs(93, 7, HERE << "will no longer write " << status());
+ state.writing = State::writingReallyDone;
virginWriteClaim.disable();
virginConsume();
-
- // Comm does not have an interface to clear the writer, but
- // writeMore() will not write if our write callback is called
- // when state.writing == State::writingDone;
}
void ICAPModXact::stopBackup()
void ICAPModXact::readMore()
{
if (reader || doneReading()) {
- debugs(32,3,HERE << "returning from readMore because reader or doneReading()");
+ debugs(93,3,HERE << "returning from readMore because reader or doneReading()");
return;
}
// do not fill readBuf if we have no space to store the result
if (!adapted->data->body->hasPotentialSpace()) {
- debugs(93,1,HERE << "Not reading because ICAP reply buffer is full");
+ debugs(93,3,HERE << "not reading because ICAP reply buffer is full");
return;
}
if (readBuf.hasSpace())
scheduleRead();
else
- debugs(93,1,HERE << "nothing to do because !readBuf.hasSpace()");
+ debugs(93,3,HERE << "nothing to do because !readBuf.hasSpace()");
}
// comm module read a portion of the ICAP response for us
adapted->sendSourceProgress();
}
- if (!from.hasContent() && state.doneReceiving) {
+ if (state.doneReceiving && claimSize(virginSendClaim) <= 0) {
debugs(93, 5, "ICAPModXact echoed all " << status());
stopSending(true);
} else {
// handle100Continue() manages state.writing on its own.
// Non-100 status means the server needs no postPreview data from us.
if (state.writing == State::writingPaused)
- stopWriting();
+ stopWriting(true);
// TODO: Consider applying a Squid 2.5 patch to recognize 201 responses
}
if (virginSendClaim.limited()) // preview only
stopBackup();
- state.parsing = State::psHttpHeader; // eventually
+ state.parsing = State::psIcapHeader; // eventually
+ icapReply->reset();
state.writing = State::writingPrime;
httpBuf.init();
packHead(httpBuf, oldHead);
- // allocate the adapted message
+ // allocate the adapted message and copy metainfo
Must(!adapted->data->header);
HttpMsg *newHead = NULL;
-
- if (dynamic_cast<const HttpRequest*>(oldHead))
- newHead = new HttpRequest;
- else
- if (dynamic_cast<const HttpReply*>(oldHead))
- newHead = new HttpReply;
-
+ if (const HttpRequest *oldR = dynamic_cast<const HttpRequest*>(oldHead)) {
+ HttpRequest *newR = new HttpRequest;
+ newR->client_addr = oldR->client_addr;
+ newHead = newR;
+ } else
+ if (dynamic_cast<const HttpReply*>(oldHead))
+ newHead = new HttpReply;
Must(newHead);
adapted->data->setHeader(newHead);
maybeAllocateHttpMsg();
if (!parseHead(adapted->data->header))
- return; // need more header data
+ return; // need more header data
}
state.parsing = State::psBody;
}
-/*
- * Common routine used to parse both HTTP and ICAP headers
- */
+// parses both HTTP and ICAP headers
bool ICAPModXact::parseHead(HttpMsg *head)
{
- assert(head);
+ Must(head);
debugs(93, 5, HERE << "have " << readBuf.contentSize() << " head bytes to parse" <<
"; state: " << state.parsing);
const bool parsed = head->parse(&readBuf, commEof, &error);
Must(parsed || !error); // success or need more data
- if (!parsed) { // need more data
+ if (!parsed) { // need more data
debugs(93, 5, HERE << "parse failed, need more data, return false");
head->reset();
return false;
if (parsed)
return true;
- debugs(32,3,HERE << this << " needsMoreData = " << bodyParser->needsMoreData());
+ debugs(93,3,HERE << this << " needsMoreData = " << bodyParser->needsMoreData());
if (bodyParser->needsMoreData()) {
- debugs(32,3,HERE << this);
+ debugs(93,3,HERE << this);
Must(mayReadMore());
readMore();
}
// internal cleanup
void ICAPModXact::doStop()
{
- debugs(98, 5, HERE << "doStop() called");
+ debugs(93, 5, HERE << "doStop() called");
ICAPXaction::doStop();
- stopWriting();
+ stopWriting(false);
stopBackup();
if (icapReply) {
// build HTTP request header, if any
ICAP::Method m = s.method;
- if (ICAP::methodRespmod == m && virgin->data->cause)
- encapsulateHead(buf, "req-hdr", httpBuf, virgin->data->cause);
- else if (ICAP::methodReqmod == m)
- encapsulateHead(buf, "req-hdr", httpBuf, virgin->data->header);
+ const HttpRequest *request = virgin->data->cause ?
+ virgin->data->cause :
+ dynamic_cast<const HttpRequest*>(virgin->data->header);
+
+ // to simplify, we could we assume that request is always available
+
+ String urlPath;
+ if (request) {
+ urlPath = request->urlpath;
+ if (ICAP::methodRespmod == m)
+ encapsulateHead(buf, "req-hdr", httpBuf, request);
+ else
+ if (ICAP::methodReqmod == m)
+ encapsulateHead(buf, "req-hdr", httpBuf, virgin->data->header);
+ }
if (ICAP::methodRespmod == m)
if (const MsgPipeData::Header *prime = virgin->data->header)
buf.append(ICAP::crlf, 2); // terminate Encapsulated line
- if (shouldPreview()) {
+ if (shouldPreview(urlPath)) {
buf.Printf("Preview: %d\r\n", (int)preview.ad());
virginSendClaim.protectUpTo(preview.ad());
}
virginSendClaim.protectAll();
}
- const HttpRequest *request = virgin->data->cause ?
- virgin->data->cause :
- dynamic_cast<const HttpRequest*>(virgin->data->header);
-
- if (TheICAPConfig.send_client_ip)
- if (request->client_addr.s_addr != any_addr.s_addr)
+ if (TheICAPConfig.send_client_ip && request)
+ if (request->client_addr.s_addr != any_addr.s_addr &&
+ request->client_addr.s_addr != no_addr.s_addr)
buf.Printf("X-Client-IP: %s\r\n", inet_ntoa(request->client_addr));
- if (TheICAPConfig.send_client_username)
+ if (TheICAPConfig.send_client_username && request)
if (request->auth_user_request)
if (request->auth_user_request->username())
buf.Printf("X-Client-Username: %s\r\n", request->auth_user_request->username());
}
// decides whether to offer a preview and calculates its size
-bool ICAPModXact::shouldPreview()
+bool ICAPModXact::shouldPreview(const String &urlPath)
{
size_t wantedSize;
return false;
}
- if (!service().wantsPreview(wantedSize)) {
- debugs(93, 5, "ICAPModXact should not offer preview");
+ if (!service().wantsPreview(urlPath, wantedSize)) {
+ debugs(93, 5, "ICAPModXact should not offer preview for " << urlPath);
return false;
}
return virginBody.size() < TheBackupLimit;
}
-// returns a temporary string depicting transaction status, for debugging
void ICAPModXact::fillPendingStatus(MemBuf &buf) const
{
+ ICAPXaction::fillPendingStatus(buf);
+
if (state.serviceWaiting)
buf.append("U", 1);
+ if (!state.doneReceiving)
+ buf.append("R", 1);
+
+ if (!doneReading())
+ buf.append("r", 1);
+
if (!state.doneWriting() && state.writing != State::writingInit)
buf.Printf("w(%d)", state.writing);
void ICAPModXact::fillDoneStatus(MemBuf &buf) const
{
+ ICAPXaction::fillDoneStatus(buf);
+
if (state.doneReceiving)
buf.append("R", 1);
return done() ? 0 : (theAd - theWritten);
}
-void ICAPPreview::wrote(size_t size, bool sawEof)
+void ICAPPreview::wrote(size_t size, bool wroteEof)
{
Must(enabled());
theWritten += size;
if (theWritten >= theAd)
- theState = stDone; // sawEof is irrelevant
+ theState = stDone; // wroteEof is irrelevant
else
- if (sawEof)
+ if (wroteEof)
theState = stIeof;
}
/*
- * $Id: ICAPModXact.h,v 1.5 2006/01/09 20:38:44 wessels Exp $
+ * $Id: ICAPModXact.h,v 1.6 2006/10/31 23:30:58 wessels Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
bool done() const; // wrote everything
bool ieof() const; // premature EOF
- void wrote(size_t size, bool sawEof);
+ void wrote(size_t size, bool wroteEof);
private:
size_t theWritten;
void startReading();
void readMore();
virtual bool doneReading() const { return commEof || state.doneParsing(); }
+ virtual bool doneWriting() const { return state.doneWriting(); }
size_t claimSize(const MemBufClaim &claim) const;
const char *claimContent(const MemBufClaim &claim) const;
void makeRequestHeaders(MemBuf &buf);
void moveRequestChunk(MemBuf &buf, size_t chunkSize);
void addLastRequestChunk(MemBuf &buf);
- void openChunk(MemBuf &buf, size_t chunkSize);
- void closeChunk(MemBuf &buf, bool ieof);
+ void openChunk(MemBuf &buf, size_t chunkSize, bool ieof);
+ void closeChunk(MemBuf &buf);
void virginConsume();
- bool shouldPreview();
+ bool shouldPreview(const String &urlPath);
bool shouldAllow204();
void prepBackup(size_t expectedSize);
void backup(const MemBuf &buf);
virtual void doStop();
void stopReceiving();
void stopSending(bool nicely);
- void stopWriting();
+ void stopWriting(bool nicely);
void stopParsing();
void stopBackup();
1; // expect no new virgin info (from the virgin pipe)
// will not write anything [else] to the ICAP server connection
- bool doneWriting() const { return writing == writingDone; }
+ bool doneWriting() const { return writing == writingReallyDone; }
// parsed entire ICAP response from the ICAP server
bool doneParsing() const { return parsing == psDone; }
// measures ICAP request writing progress
enum Writing { writingInit, writingConnect, writingHeaders,
- writingPreview, writingPaused, writingPrime, writingDone } writing;
+ writingPreview, writingPaused, writingPrime,
+ writingAlmostDone, // waiting for the last write() call to finish
+ writingReallyDone } writing;
enum Sending { sendingUndecided, sendingVirgin, sendingAdapted,
sendingDone } sending;
cb(NULL), cbData(NULL)
{
- debug(93,9)("ICAPOptXact constructed, this=%p\n", this);
}
ICAPOptXact::~ICAPOptXact()
{
Must(!options); // the caller must set to NULL
- debug(93,9)("ICAPOptXact destructed, this=%p\n", this);
}
void ICAPOptXact::start(ICAPServiceRep::Pointer &aService, Callback *aCb, void *aCbData)
{
+ ICAPXaction_Enter(start);
service(aService);
Must(!cb && aCb && aCbData);
cbData = cbdataReference(aCbData);
openConnection();
+
+ ICAPXaction_Exit();
}
void ICAPOptXact::handleCommConnected()
scheduleWrite(requestBuf);
}
+bool ICAPOptXact::doneAll() const
+{
+ return options && ICAPXaction::doneAll();
+}
+
+
void ICAPOptXact::doStop()
{
ICAPXaction::doStop();
}
}
- // get rid of options if we did call the callback
+ // get rid of options if we did not call the callback
delete options;
options = NULL;
bool ICAPOptXact::parseResponse()
{
+ debugs(93, 5, HERE << "have " << readBuf.contentSize() << " bytes to parse" <<
+ status());
+ debugs(93, 5, HERE << "\n" << readBuf.content());
+
HttpReply *r = new HttpReply;
r->protoPrefix = "ICAP/"; // TODO: make an IcapReply class?
/*
- * $Id: ICAPOptXact.h,v 1.3 2005/12/22 22:26:31 wessels Exp $
+ * $Id: ICAPOptXact.h,v 1.4 2006/10/31 23:30:58 wessels Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
virtual void handleCommConnected();
virtual void handleCommWrote(size_t size);
virtual void handleCommRead(size_t size);
+ virtual bool doneAll() const;
void makeRequest(MemBuf &buf);
bool parseResponse();
#include "squid.h"
+#include "wordlist.h"
#include "HttpReply.h"
#include "ICAPOptions.h"
#include "TextException.h"
ICAPOptions::ICAPOptions(): error("unconfigured"),
max_connections(-1), allow204(false),
- preview(-1), theTTL(-1), transfer_ext(NULL)
+ preview(-1), theTTL(-1)
{
- transfers.preview = transfers.ignore = transfers.complete = NULL;
- transfers.other = TRANSFER_NONE;
-};
+ theTransfers.preview.name = "Transfer-Preview";
+ theTransfers.preview.kind = xferPreview;
+ theTransfers.ignore.name = "Transfer-Ignore";
+ theTransfers.ignore.kind = xferIgnore;
+ theTransfers.complete.name = "Transfer-Complete";
+ theTransfers.complete.kind = xferComplete;
+
+ // Section 4.10.2 of RFC 3507 says that default is no Preview
+ // TODO: provide a squid.conf option to overwrite the default
+ theTransfers.byDefault = &theTransfers.complete;
+}
ICAPOptions::~ICAPOptions()
{
- delete transfers.preview;
- delete transfers.ignore;
- delete transfers.complete;
- delete transfer_ext;
-};
-
-ICAPOptions::transfer_type ICAPOptions::getTransferExt(const char *s)
-{
-
- if (transfer_ext) {
- List<TransferPair> *data = transfer_ext;
-
- while (data) {
- if (*(data->element.ext) == *s) {
- return data->element.type;
- }
-
- data = data->next;
- }
- }
-
- return TRANSFER_NONE;
}
-void ICAPOptions::insertTransferExt(const char *t, transfer_type t_type)
+// future optimization note: this method is called by ICAP ACL code at least
+// twice for each HTTP message to see if the message should be ignored. For any
+// non-ignored HTTP message, ICAP calls to check whether a preview is needed.
+ICAPOptions::TransferKind ICAPOptions::transferKind(const String &urlPath) const
{
- List<TransferPair> **Tail;
- TransferPair t_ext;
-
- if (t == "*") {
- transfers.other = t_type;
- return;
- }
+ if (theTransfers.preview.matches(urlPath))
+ return xferPreview;
- for (Tail = &transfer_ext; *Tail; Tail = &((*Tail)->next)) {
- if (*(*Tail)->element.ext == *t) {
- (*Tail)->element.type = t_type;
- return;
- }
- }
+ if (theTransfers.complete.matches(urlPath))
+ return xferComplete;
- t_ext.ext = xstrdup(t);
- t_ext.type = t_type;
- List<TransferPair> *q = new List<TransferPair>(t_ext);
- *(Tail) = q;
+ if (theTransfers.ignore.matches(urlPath))
+ return xferIgnore;
-};
-
-void ICAPOptions::cfgTransferListHeader(const HttpHeader *h, const char *fname, transfer_type t_type)
-{
- const String s = h->getByName(fname);
-
- if (!s.size())
- return;
-
- if (t_type == TRANSFER_PREVIEW)
- transfers.preview = parseExtFileList(s.buf(), s.buf() + s.size(), t_type);
- else if (t_type == TRANSFER_IGNORE)
- transfers.ignore = parseExtFileList(s.buf(), s.buf() + s.size(), t_type);
- else if (t_type == TRANSFER_COMPLETE)
- transfers.complete = parseExtFileList(s.buf(), s.buf() + s.size(), t_type);
- else
- fatalf("Unexpected transfer_type at %s:%d", __FILE__,__LINE__);
-}
-
-List<String> *ICAPOptions::parseExtFileList(const char *start, const char *end, transfer_type t_type)
-{
- const String s = xstrndup(start, end - start + 1);
- const char *item;
- const char *pos = NULL;
- char *fext = NULL;
- int ilen;
- String t = NULL;
-
- List<String> **Tail = NULL;
- List<String> *H = NULL;
-
- for (Tail = &H; *Tail; Tail = &((*Tail)->next))
-
- ;
- while (strListGetItem(&s, ',', &item, &ilen, &pos)) {
- fext = xstrndup(item, ilen + 1);
- t = fext;
- List<String> *q = new List<String> (t);
- *(Tail) = q;
- Tail = &q->next;
- insertTransferExt(fext, t_type);
- }
-
- return H;
+ debugs(93,7, "ICAPOptions url " << urlPath << " matches no extensions; " <<
+ "using default: " << theTransfers.byDefault->name);
+ return theTransfers.byDefault->kind;
}
bool ICAPOptions::valid() const
cfgIntHeader(h, "Preview", preview);
- cfgTransferListHeader(h, "Transfer-Preview", TRANSFER_PREVIEW);
-
- cfgTransferListHeader(h, "Transfer-Ignore", TRANSFER_IGNORE);
-
- cfgTransferListHeader(h, "Transfer-Complete", TRANSFER_COMPLETE);
+ cfgTransferList(h, theTransfers.preview);
+ cfgTransferList(h, theTransfers.ignore);
+ cfgTransferList(h, theTransfers.complete);
}
void ICAPOptions::cfgMethod(ICAP::Method m)
value = atoi(s.buf());
else
value = -1;
+
+ debugs(93,5, "ICAPOptions::cfgIntHeader " << fname << ": " << value);
+}
+
+void ICAPOptions::cfgTransferList(const HttpHeader *h, TransferList &list)
+{
+ const String buf = h->getByName(list.name);
+ bool foundStar = false;
+ list.parse(buf, foundStar);
+
+ if (foundStar) {
+ theTransfers.byDefault = &list;
+ debugs(93,5, "ICAPOptions::cfgTransferList: " <<
+ "set default transfer to " << list.name);
+ }
+
+ list.report(5, "ICAPOptions::cfgTransferList: ");
+}
+
+
+/* ICAPOptions::TransferList */
+
+ICAPOptions::TransferList::TransferList(): extensions(NULL), name(NULL),
+ kind(xferNone) {
+};
+
+ICAPOptions::TransferList::~TransferList() {
+ wordlistDestroy(&extensions);
+};
+
+void ICAPOptions::TransferList::add(const char *extension) {
+ wordlistAdd(&extensions, extension);
+};
+
+bool ICAPOptions::TransferList::matches(const String &urlPath) const {
+ const int urlLen = urlPath.size();
+ for (wordlist *e = extensions; e; e = e->next) {
+ // optimize: store extension lengths
+ const int eLen = strlen(e->key);
+
+ // assume URL contains at least '/' before the extension
+ if (eLen < urlLen) {
+ const int eOff = urlLen - eLen;
+ // RFC 3507 examples imply that extensions come without leading '.'
+ if (urlPath.buf()[eOff-1] == '.' &&
+ strcmp(urlPath.buf() + eOff, e->key) == 0) {
+ debugs(93,7, "ICAPOptions url " << urlPath << " matches " <<
+ name << " extension " << e->key);
+ return true;
+ }
+ }
+ }
+ debugs(93,8, "ICAPOptions url " << urlPath << " matches no " << name << " extensions");
+ return false;
+}
+
+void ICAPOptions::TransferList::parse(const String &buf, bool &foundStar) {
+ foundStar = false;
+
+ const char *item;
+ const char *pos = NULL;
+ int ilen;
+ while (strListGetItem(&buf, ',', &item, &ilen, &pos)) {
+ if (ilen == 1 && *item == '*')
+ foundStar = true;
+ else
+ add(xstrndup(item, ilen+1));
+ }
+}
+
+void ICAPOptions::TransferList::report(int level, const char *prefix) const {
+ if (extensions) {
+ for (wordlist *e = extensions; e; e = e->next)
+ debugs(93,level, prefix << name << ": " << e->key);
+ } else {
+ debugs(93,level, prefix << "no " << name << " extensions");
+ }
}
/*
- * $Id: ICAPOptions.h,v 1.7 2006/02/16 20:44:07 wessels Exp $
+ * $Id: ICAPOptions.h,v 1.8 2006/10/31 23:30:58 wessels Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
#define SQUID_ICAPOPTIONS_H
#include "squid.h"
-#include "List.h"
#include "ICAPClient.h"
+class wordlist;
+
/* Maintains options supported by a given ICAP service.
* See RFC 3507, Section "4.10.2 OPTIONS Response". */
int ttl() const { return theTTL; };
- typedef enum { TRANSFER_NONE, TRANSFER_PREVIEW, TRANSFER_IGNORE, TRANSFER_COMPLETE } transfer_type;
- transfer_type getTransferExt(const char *);
+ typedef enum { xferNone, xferPreview, xferIgnore, xferComplete } TransferKind;
+ TransferKind transferKind(const String &urlPath) const;
public:
const char *error; // human-readable information; set iff !valid()
bool allow204;
int preview;
- // varios Transfer-* lists
+protected:
+ // Transfer-* extension list representation
+ // maintains wordlist and does parsing/matching
+ class TransferList {
+ public:
+ TransferList();
+ ~TransferList();
+
+ bool matches(const String &urlPath) const;
+
+ void parse(const String &buf, bool &foundStar);
+ void add(const char *extension);
+ void report(int level, const char *prefix) const;
+
+ public:
+ wordlist *extensions; // TODO: optimize with a hash of some sort
+ const char *name; // header name, mostly for debugging
+ TransferKind kind; // to simplify caller's life
+ };
+ // varios Transfer-* lists
struct Transfers
{
- List<String> *preview;
- List<String> *ignore;
- List<String> *complete;
- transfer_type other; // default X from Transfer-X: *
- }
-
- transfers;
+ TransferList preview;
+ TransferList ignore;
+ TransferList complete;
+ TransferList *byDefault; // Transfer-X that has '*'
+ } theTransfers;
-protected:
int theTTL;
time_t theTimestamp;
- // The list of pairs "file extension <-> transfer type"
-
- struct TransferPair
- {
- char *ext;
- transfer_type type;
- };
-
- List<TransferPair> *transfer_ext;
-
private:
void cfgMethod(ICAP::Method m);
void cfgIntHeader(const HttpHeader *h, const char *fname, int &value);
- void insertTransferExt(const char *t, transfer_type t_type);
- void cfgTransferListHeader(const HttpHeader *h, const char *fname, transfer_type type);
- List<String> *parseExtFileList(const char *start, const char *end, transfer_type t_type);
+ void cfgTransferList(const HttpHeader *h, TransferList &l);
};
CBDATA_CLASS_INIT(ICAPServiceRep);
+// XXX: move to squid.conf
+const int ICAPServiceRep::TheSessionFailureLimit = 10;
+
ICAPServiceRep::ICAPServiceRep(): method(ICAP::methodNone),
- point(ICAP::pointNone), port(-1), bypass(false), unreachable(false),
- theOptions(NULL), theState(stateInit), notifying(false), self(NULL)
+ point(ICAP::pointNone), port(-1), bypass(false),
+ theOptions(NULL), theLastUpdate(0),
+ theSessionFailures(0), isSuspended(0),
+ waiting(false), notifying(false),
+ updateScheduled(false), self(NULL),
+ wasAnnouncedUp(true) // do not announce an "up" service at startup
{}
ICAPServiceRep::~ICAPServiceRep()
{
- Must(!waiting());
+ Must(!waiting);
changeOptions(0);
}
void ICAPServiceRep::invalidate()
{
assert(self != NULL);
- self = NULL; // may destroy us and, hence, invalidate cbdata(this)
+ Pointer savedSelf = self; // to prevent destruction when we nullify self
+ self = NULL;
+
+ announceStatusChange("invalidated by reconfigure", false);
+
+ savedSelf = NULL; // may destroy us and, hence, invalidate cbdata(this)
// TODO: it would be nice to invalidate cbdata(this) when not destroyed
}
+void ICAPServiceRep::noteFailure() {
+ ++theSessionFailures;
+ debugs(93,4, "ICAPService failure " << theSessionFailures <<
+ ", out of " << TheSessionFailureLimit << " allowed");
+
+ if (theSessionFailures > TheSessionFailureLimit)
+ suspend("too many failures");
+
+ // TODO: Should bypass setting affect how much Squid tries to talk to
+ // the ICAP service that is currently unusable and is likely to remain
+ // so for some time? The current code says "no". Perhaps the answer
+ // should be configurable.
+}
+
+void ICAPServiceRep::suspend(const char *reason) {
+ if (isSuspended) {
+ debugs(93,4, "keeping ICAPService suspended, also for " << reason);
+ } else {
+ isSuspended = reason;
+ debugs(93,1, "suspending ICAPService for " << reason);
+ announceStatusChange("suspended", true);
+ }
+}
+
+bool ICAPServiceRep::probed() const
+{
+ return theLastUpdate != 0;
+}
+
+bool ICAPServiceRep::hasOptions() const {
+ return theOptions && theOptions->valid() && theOptions->fresh();
+}
+
bool ICAPServiceRep::up() const
{
- return self != NULL && theState == stateUp;
+ return self != NULL && !isSuspended && hasOptions();
+}
+
+bool ICAPServiceRep::broken() const
+{
+ return probed() && !up();
+}
+
+bool ICAPServiceRep::wantsUrl(const String &urlPath) const
+{
+ Must(hasOptions());
+ return theOptions->transferKind(urlPath) != ICAPOptions::xferIgnore;
}
-bool ICAPServiceRep::wantsPreview(size_t &wantedSize) const
+bool ICAPServiceRep::wantsPreview(const String &urlPath, size_t &wantedSize) const
{
- Must(up());
+ Must(hasOptions());
if (theOptions->preview < 0)
return false;
+ if (theOptions->transferKind(urlPath) != ICAPOptions::xferPreview)
+ return false;
+
wantedSize = theOptions->preview;
return true;
bool ICAPServiceRep::allows204() const
{
- Must(up());
+ Must(hasOptions());
return true; // in the future, we may have ACLs to prevent 204s
}
void ICAPServiceRep::noteTimeToUpdate()
{
- if (!self || waiting()) {
+ if (self != NULL)
+ updateScheduled = false;
+
+ if (!self || waiting) {
debugs(93,5, "ICAPService ignores options update " << status());
return;
}
{
Must(cb);
Must(self != NULL);
+ Must(!broken()); // we do not wait for a broken service
Client i;
i.service = self;
i.data = cbdataReference(data);
theClients.push_back(i);
- if (waiting() || notifying)
+ if (waiting || notifying)
return; // do nothing, we will be picked up in noteTimeToNotify()
if (needNewOptions())
eventAdd("ICAPServiceRep::noteTimeToNotify", &ICAPServiceRep_noteTimeToNotify, this, 0, 0, true);
}
-bool ICAPServiceRep::waiting() const
-{
- return theState == stateWait;
-}
-
bool ICAPServiceRep::needNewOptions() const
{
- return !theOptions || !theOptions->fresh();
+ return self != NULL && !up();
}
void ICAPServiceRep::changeOptions(ICAPOptions *newOptions)
{
debugs(93,9, "ICAPService changes options from " << theOptions << " to " <<
newOptions);
+
delete theOptions;
theOptions = newOptions;
+ theSessionFailures = 0;
+ isSuspended = 0;
+ theLastUpdate = squid_curtime;
+
+ checkOptions();
+ announceStatusChange("down after an options fetch failure", true);
+}
+void ICAPServiceRep::checkOptions()
+{
if (theOptions == NULL)
return;
bool method_found = false;
String method_list;
Vector <ICAP::Method>::iterator iter = theOptions->methods.begin();
- debugs(0,0,HERE);
while (iter != theOptions->methods.end()) {
- debugs(0,0,HERE);
if (*iter == method) {
method_found = true;
method_list.append(ICAP::methodStr(*iter));
method_list.append(" ", 1);
iter++;
- debugs(0,0,HERE);
}
- debugs(0,0,HERE);
-
if (!method_found) {
debugs(93,1, "WARNING: Squid is configured to use ICAP method " <<
ICAP::methodStr(method) <<
* Check the ICAP server's date header for clock skew
*/
int skew = abs((int)(theOptions->timestamp() - squid_curtime));
-
if (skew > theOptions->ttl())
debugs(93, 1, host.buf() << "'s clock is skewed by " << skew << " seconds!");
+}
-#if 0
-
- List<String> *tmp;
-
- for (tmp = theOptions->transfers.preview; tmp; tmp=tmp->next)
- debugs(93,1,"Transfer-Preview: " << tmp->element.buf());
-
- for (tmp = theOptions->transfers.ignore; tmp; tmp=tmp->next)
- debugs(93,1,"Transfer-Ignore: " << tmp->element.buf());
+void ICAPServiceRep::announceStatusChange(const char *downPhrase, bool important) const
+{
+ if (wasAnnouncedUp == up()) // no significant changes to announce
+ return;
- for (tmp = theOptions->transfers.complete; tmp; tmp=tmp->next)
- debugs(93,1,"Transfer-Complete: " << tmp->element.buf());
+ const char *what = bypass ? "optional" : "essential";
+ const char *state = wasAnnouncedUp ? downPhrase : "up";
+ const int level = important ? 1 : 2;
+ debugs(93,level, what << " ICAP service is " << state << ": " << uri);
-#endif
+ wasAnnouncedUp = !wasAnnouncedUp;
}
static
void ICAPServiceRep::noteNewOptions(ICAPOptXact *x)
{
Must(x);
- Must(waiting());
-
- theState = stateDown; // default in case we fail to set new options
+ Must(waiting);
+ waiting = false;
changeOptions(x->options);
x->options = NULL;
delete x;
- if (theOptions && theOptions->valid())
- theState = stateUp;
-
- debugs(93,6, "ICAPService got new options and is now " <<
- (up() ? "up" : "down"));
+ debugs(93,3, "ICAPService got new options and is now " << status());
scheduleUpdate();
-
scheduleNotification();
}
void ICAPServiceRep::startGettingOptions()
{
+ Must(!waiting);
debugs(93,6, "ICAPService will get new options " << status());
- theState = stateWait;
+ waiting = true;
ICAPOptXact *x = new ICAPOptXact;
x->start(self, &ICAPServiceRep_noteNewOptions, this);
- // TODO: timeout incase ICAPOptXact never calls us back?
+ // TODO: timeout in case ICAPOptXact never calls us back?
}
void ICAPServiceRep::scheduleUpdate()
{
- int delay = -1;
+ if (updateScheduled)
+ return; // already scheduled
+
+ // XXX: move hard-coded constants from here to TheICAPConfig
+
+ // conservative estimate of how long the OPTIONS transaction will take
+ const int expectedWait = 20; // seconds
+
+ time_t when = 0;
if (theOptions && theOptions->valid()) {
const time_t expire = theOptions->expire();
+ debugs(93,7, "ICAPService options expire on " << expire << " >= " << squid_curtime);
- if (expire > squid_curtime)
- delay = expire - squid_curtime;
+ if (expire < 0) // unknown expiration time
+ when = squid_curtime + 60*60;
else
- if (expire >= 0)
- delay = 1; // delay for expired or 'expiring now' options
- else
- delay = 60*60; // default for options w/o known expiration time
+ if (expire < expectedWait) // invalid expiration time
+ when = squid_curtime + 60*60;
+ else
+ when = expire - expectedWait; // before the current options expire
} else {
- delay = 5*60; // delay for a down service
+ when = squid_curtime + 3*60; // delay for a down service
}
- if (delay <= 0) {
- debugs(93,0, "internal error: ICAPServiceRep failed to compute options update schedule");
- delay = 5*60; // delay for an internal error
- }
+ debugs(93,7, "ICAPService options raw update on " << when << " or " << (when - squid_curtime));
+ if (when < squid_curtime)
+ when = squid_curtime;
+
+ const int minUpdateGap = 1*60; // seconds
+ if (when < theLastUpdate + minUpdateGap)
+ when = theLastUpdate + minUpdateGap;
- // with zero delay, the state changes to stateWait before
- // notifications are sent out to clients
- assert(delay > 0);
+ // TODO: keep the time of the last update to prevet too-frequent updates
- debugs(93,7, "ICAPService will update options in " << delay << " sec");
+ const int delay = when - squid_curtime;
+
+ debugs(93,5, "ICAPService will update options in " << delay << " sec");
eventAdd("ICAPServiceRep::noteTimeToUpdate",
&ICAPServiceRep_noteTimeToUpdate, this, delay, 0, true);
-
- // XXX: prompt updates of valid options should not disable concurrent ICAP
- // xactions. 'Wait' state should not mark the service 'down'! This will
- // also remove 'delay == 0' as a special case above.
+ updateScheduled = true;
}
+// returns a temporary string depicting service status, for debugging
const char *ICAPServiceRep::status() const
{
+ static MemBuf buf;
+
+ buf.reset();
+ buf.append("[", 1);
+
+ if (up())
+ buf.append("up", 2);
+ else
+ buf.append("down", 4);
+
if (!self)
- return "[invalidated]";
+ buf.append(",gone", 5);
- switch (theState) {
+ if (waiting)
+ buf.append(",wait", 5);
- case stateInit:
- return "[init]";
+ if (notifying)
+ buf.append(",notif", 6);
- case stateWait:
- return "[wait]";
+ if (theSessionFailures > 0)
+ buf.Printf(",F%d", theSessionFailures);
- case stateUp:
- return "[up]";
+ if (isSuspended)
+ buf.append(",susp", 5);
- case stateDown:
- return "[down]";
- }
+ buf.append("]", 1);
+ buf.terminate();
- return "[unknown]";
+ return buf.content();
}
/*
- * $Id: ICAPServiceRep.h,v 1.4 2006/08/21 00:50:45 robertc Exp $
+ * $Id: ICAPServiceRep.h,v 1.5 2006/10/31 23:30:58 wessels Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
/* The ICAP service representative maintains information about a single ICAP
service that Squid communicates with. The representative initiates OPTIONS
requests to the service to keep cached options fresh. One ICAP server may
- host many ICAP services */
+ host many ICAP services. */
+
+/*
+ * A service is "up" if there is a fresh cached OPTIONS response and is
+ * "down" otherwise. A service is "probed" if we tried to get an OPTIONS
+ * response from it and succeeded or failed. A probed down service is
+ * called "broken".
+ *
+ * As a bootstrapping mechanism, ICAP transactions wait for an unprobed
+ * service to get a fresh OPTIONS response (see the callWhenReady method).
+ * The waiting callback is called when the OPTIONS transaction completes,
+ * even if the service is now broken.
+ *
+ * We do not initiate ICAP transactions with a broken service, but will
+ * eventually retry to fetch its options in hope to bring the service up.
+ *
+ * A service that should no longer be used after Squid reconfiguration is
+ * treated as if it does not have a fresh cached OPTIONS response. We do
+ * not try to fetch fresh options for such a service. It should be
+ * auto-destroyed by refcounting when no longer used.
+ */
+
class ICAPServiceRep : public RefCountable
{
const char *methodStr() const;
const char *vectPointStr() const;
- bool up() const;
+ bool probed() const; // see comments above
+ bool broken() const; // see comments above
+ bool up() const; // see comments above
- /* Service is "up" iff there is a fresh cached OPTIONS response. To
- get an OPTIONS response, ICAPServiceRep does an OPTIONS
- transaction. Failed transaction results in a "down" service. The
- Callback is called if/once the service is in a steady ("up" or
- "down") state. */
typedef void Callback(void *data, Pointer &service);
void callWhenReady(Callback *cb, void *data);
-
// the methods below can only be called on an up() service
-
- bool wantsPreview(size_t &wantedSize) const;
+ bool wantsUrl(const String &urlPath) const;
+ bool wantsPreview(const String &urlPath, size_t &wantedSize) const;
bool allows204() const;
+ void noteFailure(); // called by transactions to report service failure
+
public:
String key;
ICAP::Method method;
int port;
String resource;
- // non-options flags; TODO: check that both are used.
+ // XXX: use it when selecting a service and handling ICAP errors!
bool bypass;
- bool unreachable;
public: // treat these as private, they are for callbacks only
void noteTimeToUpdate();
Clients theClients; // all clients waiting for a call back
ICAPOptions *theOptions;
+ time_t theLastUpdate; // time the options were last updated
+
+ static const int TheSessionFailureLimit;
+ int theSessionFailures;
+ const char *isSuspended; // also stores suspension reason for debugging
- typedef enum { stateInit, stateWait, stateUp, stateDown } State;
- State theState;
+ bool waiting; // for an OPTIONS transaction to finish
bool notifying; // may be true in any state except for the initial
+ bool updateScheduled; // time-based options update has been scheduled
private:
ICAP::Method parseMethod(const char *) const;
ICAP::VectPoint parseVectPoint(const char *) const;
- bool waiting() const;
+ void suspend(const char *reason);
+
+ bool hasOptions() const;
bool needNewOptions() const;
+ void scheduleUpdate();
void scheduleNotification();
- void changeOptions(ICAPOptions *newOptions);
+
void startGettingOptions();
- void scheduleUpdate();
+ void changeOptions(ICAPOptions *newOptions);
+ void checkOptions();
+
+ void announceStatusChange(const char *downPhrase, bool important) const;
const char *status() const;
Pointer self;
+ mutable bool wasAnnouncedUp; // prevent sequential same-state announcements
CBDATA_CLASS2(ICAPServiceRep);
};
}
static
-void ICAPXaction_noteCommWrote(int, char *, size_t size, comm_err_t status, void *data)
+void ICAPXaction_noteCommWrote(int, char *, size_t size, comm_err_t status, int xerrno, void *data)
{
ICAPXaction_fromData(data).noteCommWrote(status, size);
}
theService(NULL),
inCall(NULL)
{
+ debug(93,3)("%s constructed, this=%p\n", typeName, this);
readBuf.init(SQUID_TCP_SO_RCVBUF, SQUID_TCP_SO_RCVBUF);
commBuf = (char*)memAllocBuf(SQUID_TCP_SO_RCVBUF, &commBufSize);
// make sure maximum readBuf space does not exceed commBuf size
ICAPXaction::~ICAPXaction()
{
+ debug(93,3)("%s destructing, this=%p\n", typeName, this);
doStop();
readBuf.clean();
memFreeBuf(commBufSize, commBuf);
COMM_NONBLOCKING, s.uri.buf());
if (connection < 0)
- throw TexcHere("cannot connect to ICAP service " /* + uri */);
+ dieOnConnectionFailure(); // throws
}
debugs(93,3, typeName << " opens connection to " << s.host.buf() << ":" << s.port);
void ICAPXaction::closeConnection()
{
if (connection >= 0) {
- commSetTimeout(connection, -1, NULL, NULL);
if (closer) {
comm_remove_close_handler(connection, closer, this);
closer = NULL;
}
- cancelRead();
+ cancelRead(); // may not work
+
+ if (reuseConnection && (writer || reader)) {
+ debugs(93,5, HERE << "not reusing pconn due to pending I/O " << status());
+ reuseConnection = false;
+ }
if (reuseConnection) {
- debugs(93,3, HERE << "pushing pconn " << connection);
+ debugs(93,3, HERE << "pushing pconn " << status());
+ commSetTimeout(connection, -1, NULL, NULL);
icapPconnPool->push(connection, theService->host.buf(), theService->port, NULL);
} else {
- debugs(93,3, HERE << "closing pconn " << connection);
+ debugs(93,3, HERE << "closing pconn " << status());
+ // comm_close will clear timeout
comm_close(connection);
}
+ writer = NULL;
+ reader = NULL;
connector = NULL;
connection = -1;
}
Must(connector);
connector = NULL;
- Must(commStatus == COMM_OK);
+
+ if (commStatus != COMM_OK)
+ dieOnConnectionFailure(); // throws
+
+ fd_table[connection].noteUse(icapPconnPool);
handleCommConnected();
ICAPXaction_Exit();
}
+void ICAPXaction::dieOnConnectionFailure() {
+ theService->noteFailure();
+ debugs(93,3, typeName << " failed to connect to the ICAP service at " <<
+ service().uri);
+ throw TexcHere("cannot connect to the ICAP service");
+}
+
void ICAPXaction::scheduleWrite(MemBuf &buf)
{
// comm module will free the buffer
- writer = (IOCB *)&ICAPXaction_noteCommWrote;
+ writer = &ICAPXaction_noteCommWrote;
comm_write_mbuf(connection, &buf, writer, this);
- fd_table[connection].noteUse(icapPconnPool);
- commSetTimeout(connection, 61, &ICAPXaction_noteCommTimedout, this);
+ updateTimeout();
}
void ICAPXaction::noteCommWrote(comm_err_t commStatus, size_t size)
Must(commStatus == COMM_OK);
+ updateTimeout();
+
handleCommWrote(size);
ICAPXaction_Exit();
bool ICAPXaction::done() const
{
- if (stopReason != NULL) { // mustStop() has been called
- debugs(93,1,HERE << "ICAPXaction is done() because " << stopReason);
- return true;
- }
-
- return doneAll();
+ // stopReason, set in mustStop(), overwrites all other conditions
+ return stopReason != NULL || doneAll();
}
bool ICAPXaction::doneAll() const
return !connector && !reader && !writer;
}
+void ICAPXaction::updateTimeout() {
+ if (reader || writer) {
+ // restart the timeout before each I/O
+ // XXX: why does Config.Timeout lacks a write timeout?
+ commSetTimeout(connection, Config.Timeout.read,
+ &ICAPXaction_noteCommTimedout, this);
+ } else {
+ // clear timeout when there is no I/O
+ // Do we need a lifetime timeout?
+ commSetTimeout(connection, -1, NULL, NULL);
+ }
+}
+
void ICAPXaction::scheduleRead()
{
Must(connection >= 0);
*/
comm_read(connection, commBuf, readBuf.spaceSize(), reader, this);
- commSetTimeout(connection, 61, &ICAPXaction_noteCommTimedout, this);
+ updateTimeout();
}
// comm module read a portion of the ICAP response for us
Must(commStatus == COMM_OK);
Must(sz >= 0);
+ updateTimeout();
+
debugs(93, 3, HERE << "read " << sz << " bytes");
/*
// These checks try to mimic the comm_read_cancel() assertions.
if (comm_has_pending_read(connection) &&
- !comm_has_pending_read_callback(connection))
+ !comm_has_pending_read_callback(connection)) {
comm_read_cancel(connection, reader, this);
-
- reader = NULL;
+ reader = NULL;
+ }
}
}
return commEof;
}
+bool ICAPXaction::doneWriting() const
+{
+ return !writer;
+}
+
+bool ICAPXaction::doneWithIo() const
+{
+ return connection >= 0 && // or we could still be waiting to open it
+ !connector && !reader && !writer && // fast checks, some redundant
+ doneReading() && doneWriting();
+}
+
void ICAPXaction::mustStop(const char *aReason)
{
Must(inCall); // otherwise nobody will call doStop()
- Must(!stopReason);
Must(aReason);
- stopReason = aReason;
- debugs(93, 5, typeName << " will stop, reason: " << stopReason);
+ if (!stopReason) {
+ stopReason = aReason;
+ debugs(93, 5, typeName << " will stop, reason: " << stopReason);
+ } else {
+ debugs(93, 5, typeName << " will stop, another reason: " << aReason);
+ }
}
// internal cleanup
debugs(93, 4, typeName << "::" << inCall << " caught an exception: " <<
e.message << ' ' << status());
- if (!done())
- mustStop("exception");
+ reuseConnection = false; // be conservative
+ mustStop("exception");
}
void ICAPXaction::callEnd()
status());
doStop(); // may delete us
return;
+ } else
+ if (doneWithIo()) {
+ debugs(93, 5, HERE << typeName << " done with I/O " << status());
+ closeConnection();
}
debugs(93, 6, typeName << "::" << inCall << " ended " << status());
/*
- * $Id: ICAPXaction.h,v 1.8 2006/09/19 17:17:52 serassio Exp $
+ * $Id: ICAPXaction.h,v 1.9 2006/10/31 23:30:58 wessels Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
void openConnection();
void closeConnection();
+ void dieOnConnectionFailure();
+
void scheduleRead();
void scheduleWrite(MemBuf &buf);
+ void updateTimeout();
void cancelRead();
bool parseHttpMsg(HttpMsg *msg); // true=success; false=needMore; throw=err
bool mayReadMore() const;
virtual bool doneReading() const;
+ virtual bool doneWriting() const;
+ bool doneWithIo() const;
bool done() const;
virtual bool doneAll() const;
// - open the try clause;
// - call callStart().
#define ICAPXaction_Enter(method) \
- try { \
- if (!callStart(#method)) \
- return;
+ try { \
+ if (!callStart(#method)) \
+ return;
// asynchronous call exit:
// - close the try clause;
// - catch exceptions;
// - let callEnd() handle transaction termination conditions
#define ICAPXaction_Exit() \
- } \
- catch (const TextException &e) { \
- callException(e); \
- } \
- callEnd();
+ } \
+ catch (const TextException &e) { \
+ callException(e); \
+ } \
+ callEnd();
#endif /* SQUID_ICAPXACTION_H */
#define MsgPipe_MAKE_CALLBACK(callName, destination) \
static \
void MsgPipe_send ## callName(void *p) { \
- MsgPipe *pipe = static_cast<MsgPipe*>(p); \
- if (pipe && pipe->canSend(pipe->destination, #callName, false)) \
- pipe->destination->note##callName(pipe); \
+ MsgPipe *pipe = static_cast<MsgPipe*>(p); \
+ if (pipe && pipe->canSend(pipe->destination, #callName, false)) \
+ pipe->destination->note##callName(pipe); \
}
// static event callbacks
const char *verb = future ?
(res ? "will send " : "wont send ") :
(res ? "sends " : "ignores ");
- debugs(99,5, "MsgPipe " << name << "(" << this << ") " <<
+ debugs(93,5, "MsgPipe " << name << "(" << this << ") " <<
verb << callName << " to the " <<
(destination ? destination->kind() : "destination") << "(" <<
destination << "); " <<
/*
- * $Id: MsgPipeData.h,v 1.7 2006/02/17 18:11:00 wessels Exp $
+ * $Id: MsgPipeData.h,v 1.8 2006/10/31 23:30:58 wessels Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
{
public:
- MsgPipeData(): header(0), body(0), cause(0) {};
+ MsgPipeData(): header(0), body(0), cause(0) {}
~MsgPipeData()
{
body->clean();
delete body;
}
- };
+ }
void setCause(HttpRequest *r)
{
- HTTPMSGUNLOCK(cause);
- cause = HTTPMSGLOCK(r);
- };
+ if (r) {
+ HTTPMSGUNLOCK(cause);
+ cause = HTTPMSGLOCK(r);
+ } else {
+ assert(!cause);
+ }
+ }
void setHeader(HttpMsg *msg)
{
HTTPMSGUNLOCK(header);
header = HTTPMSGLOCK(msg);
- };
+ }
public:
typedef HttpMsg Header;
//inline
//ostream &operator <<(ostream &os, const TextException &exx) {
-// return exx.print(os);
+// return exx.print(os);
//}
#if !defined(TexcHere)
-# define TexcHere(msg) TextException((msg), __FILE__, __LINE__)
+# define TexcHere(msg) TextException((msg), __FILE__, __LINE__)
#endif
extern void Throw(const char *message, const char *fileName, int lineNo);
// Must(condition) is like assert(condition) but throws an exception instead
#if !defined(Must)
-# define Must(cond) ((cond) ? \
- (void)0 : \
- (void)Throw(#cond, __FILE__, __LINE__))
+# define Must(cond) ((cond) ? \
+ (void)0 : \
+ (void)Throw(#cond, __FILE__, __LINE__))
#endif
#endif /* SQUID__TEXTEXCEPTION_H */
#
# Makefile for the Squid Object Cache server
#
-# $Id: Makefile.am,v 1.171 2006/09/15 15:01:25 hno Exp $
+# $Id: Makefile.am,v 1.172 2006/10/31 23:30:56 wessels Exp $
#
# Uncomment and customize the following to suit your needs:
#
ICAP/ChunkedCodingParser.h \
ICAP/ICAPClient.cc \
ICAP/ICAPClient.h \
+ ICAP/ICAPClientVector.cc \
+ ICAP/ICAPClientVector.h \
ICAP/ICAPClientReqmodPrecache.cc \
ICAP/ICAPClientReqmodPrecache.h \
ICAP/ICAPClientRespmodPrecache.cc \
/*
- * $Id: Server.cc,v 1.6 2006/09/20 22:26:24 hno Exp $
+ * $Id: Server.cc,v 1.7 2006/10/31 23:30:56 wessels Exp $
*
* DEBUG:
* AUTHOR: Duane Wessels
fwd = NULL; // refcounted
#if ICAP_CLIENT
- if (icap)
+ if (icap) {
+ debug(11,5)("ServerStateData destroying icap=%p\n", icap);
+ icap->ownerAbort();
delete icap;
+ }
#endif
}
#if ICAP_CLIENT
/*
- * Initiate an ICAP transaction. Return 0 if all is well, or -1 upon error.
+ * Initiate an ICAP transaction. Return true on success.
* Caller will handle error condition by generating a Squid error message
* or take other action.
*/
-int
-ServerStateData::doIcap(ICAPServiceRep::Pointer service)
+bool
+ServerStateData::startIcap(ICAPServiceRep::Pointer service)
{
- debug(11,5)("ServerStateData::doIcap() called\n");
+ debug(11,5)("ServerStateData::startIcap() called\n");
+ if (!service) {
+ debug(11,3)("ServerStateData::startIcap fails: lack of service\n");
+ return false;
+ }
+ if (service->broken()) {
+ debug(11,3)("ServerStateData::startIcap fails: broken service\n");
+ return false;
+ }
assert(NULL == icap);
icap = new ICAPClientRespmodPrecache(service);
- return 0;
+ return true;
}
#endif
/*
- * $Id: Server.h,v 1.1 2006/01/25 17:47:26 wessels Exp $
+ * $Id: Server.h,v 1.2 2006/10/31 23:30:56 wessels Exp $
*
* AUTHOR: Duane Wessels
*
virtual ~ServerStateData();
#if ICAP_CLIENT
-
- virtual void takeAdaptedHeaders(HttpReply *) = 0;
- virtual void takeAdaptedBody(MemBuf *) = 0;
- virtual void doneAdapting() = 0;
+ virtual bool takeAdaptedHeaders(HttpReply *) = 0;
+ virtual bool takeAdaptedBody(MemBuf *) = 0;
+ virtual void finishAdapting() = 0;
virtual void abortAdapting() = 0;
virtual void icapSpaceAvailable() = 0;
virtual void icapAclCheckDone(ICAPServiceRep::Pointer) = 0;
ICAPClientRespmodPrecache *icap;
bool icapAccessCheckPending;
- int doIcap(ICAPServiceRep::Pointer);
+ bool startIcap(ICAPServiceRep::Pointer);
#endif
};
/*
- * $Id: client_side.cc,v 1.743 2006/10/19 01:39:40 wessels Exp $
+ * $Id: client_side.cc,v 1.744 2006/10/31 23:30:56 wessels Exp $
*
* DEBUG: section 33 Client-side Routines
* AUTHOR: Duane Wessels
debugs(33,3,HERE << "clientReadBody in.notYetUsed " << conn->in.notYetUsed);
if (size > conn->in.notYetUsed)
- size = conn->in.notYetUsed;
+ size = conn->in.notYetUsed; // may make size zero
debugs(33,3,HERE << "clientReadBody actual size " << size);
- assert(size);
-
- mb.append(conn->in.buf, size);
-
- connNoteUseOfBuffer(conn, size);
+ if (size > 0) {
+ mb.append(conn->in.buf, size);
+ connNoteUseOfBuffer(conn, size);
+ }
return size;
}
/*
- * $Id: client_side_request.cc,v 1.76 2006/10/19 00:35:35 wessels Exp $
+ * $Id: client_side_request.cc,v 1.77 2006/10/31 23:30:57 wessels Exp $
*
* DEBUG: section 85 Client-side Request Routines
* AUTHOR: Robert Collins (Originally Duane Wessels in client_side.c)
* to the user, or keep going without ICAP.
*/
fatal("Fix this case in ClientRequestContext::icapAclCheckDone()");
+ // And when fixed, check whether the service is down in doIcap and
+ // if it is, abort early, without creating ICAPClientReqmodPrecache.
+ // See Server::startIcap() and its use.
http->doCallouts();
}
icap->body_reader->bytes_read += size_to_send;
debugs(32,3," HTTP client body bytes_read=" << icap->body_reader->bytes_read);
} else {
- debugs(32,0,HERE << "cannot send body data to ICAP");
- debugs(32,0,HERE << "\tBodyReader MemBuf has " << mb.contentSize());
- debugs(32,0,HERE << "\tbut icap->potentialSpaceSize() is " << icap->potentialSpaceSize());
+ debugs(32,2,HERE << "cannot send body data to ICAP");
+ debugs(32,2,HERE << "\tBodyReader MemBuf has " << mb.contentSize());
+ debugs(32,2,HERE << "\tbut icap->potentialSpaceSize() is " << icap->potentialSpaceSize());
return;
}
/*
- * $Id: comm.cc,v 1.427 2006/09/25 15:04:07 adrian Exp $
+ * $Id: comm.cc,v 1.428 2006/10/31 23:30:57 wessels Exp $
*
* DEBUG: section 5 Socket Functions
* AUTHOR: Harvest Derived
void
fdc_t::acceptOne(int fd) {
+ // If there is no callback and we accept, we will leak the accepted FD.
+ // When we are running out of FDs, there is often no callback.
+ if (!accept.accept.callback.handler) {
+ debug (5,5) ("fdc_t::acceptOne orphaned: FD %d\n", fd);
+ // XXX: can we remove this and similar "just in case" calls and
+ // either listen always or listen only when there is a callback?
+ if (!AcceptLimiter::Instance().deferring())
+ commSetSelect(fd, COMM_SELECT_READ, comm_accept_try, NULL, 0);
+ accept.accept.finished(true);
+ return;
+ }
+
/*
* We don't worry about running low on FDs here. Instead,
* httpAccept() will use AcceptLimiter if we reach the limit
if (newfd < 0) {
if (newfd == COMM_NOMESSAGE) {
/* register interest again */
+ debug (5,5) ("fdc_t::acceptOne eof: FD %d handler: %p\n", fd, (void*)accept.accept.callback.handler);
commSetSelect(fd, COMM_SELECT_READ, comm_accept_try, NULL, 0);
accept.accept.finished(true);
return;
return;
}
+ debug (5,5) ("fdc_t::acceptOne accepted: FD %d handler: %p newfd: %d\n", fd, (void*)accept.accept.callback.handler, newfd);
+
+ assert(accept.accept.callback.handler);
accept.accept.doCallback(fd, newfd, COMM_OK, 0, &accept.connDetails);
/* If we weren't re-registed, don't bother trying again! */
*/
void
comm_accept(int fd, IOACB *handler, void *handler_data) {
+ debug (5,5) ("comm_accept: FD %d handler: %p\n", fd, (void*)handler);
requireOpenAndActive(fd);
/* make sure we're not pending! */
return Instance_;
}
+bool
+AcceptLimiter::deferring() const {
+ return deferred.size() > 0;
+}
+
void
AcceptLimiter::defer (int fd, Acceptor::AcceptorFunction *aFunc, void *data) {
+ debug (5,5) ("AcceptLimiter::defer: FD %d handler: %p\n", fd, (void*)aFunc);
Acceptor temp;
temp.theFunction = aFunc;
temp.acceptFD = fd;
void
AcceptLimiter::kick() {
- if (!deferred.size())
+ if (!deferring())
return;
/* Yes, this means the first on is the last off....
void defer (int, Acceptor::AcceptorFunction *, void *);
void kick();
+ bool deferring() const;
+
private:
static AcceptLimiter Instance_;
Vector<Acceptor> deferred;
/*
- * $Id: ftp.cc,v 1.407 2006/09/19 07:56:57 adrian Exp $
+ * $Id: ftp.cc,v 1.408 2006/10/31 23:30:57 wessels Exp $
*
* DEBUG: section 9 File Transfer Protocol (FTP)
* AUTHOR: Harvest Derived
public:
void icapAclCheckDone(ICAPServiceRep::Pointer);
- void takeAdaptedHeaders(HttpReply *);
- void takeAdaptedBody(MemBuf *);
- void doneAdapting();
- void abortAdapting();
- void icapSpaceAvailable();
+ virtual bool takeAdaptedHeaders(HttpReply *);
+ virtual bool takeAdaptedBody(MemBuf *);
+ virtual void finishAdapting();
+ virtual void abortAdapting();
+ virtual void icapSpaceAvailable();
bool icapAccessCheckPending;
+private:
+ void backstabAdapter();
+ void endAdapting();
#endif
};
{
icapAccessCheckPending = false;
- if (service == NULL) {
- // handle case where no service is selected;
+ const bool startedIcap = startIcap(service);
+
+ if (!startedIcap && (!service || service->bypass)) {
+ // handle ICAP start failure when no service was selected
+ // or where the selected service was optional
entry->replaceHttpReply(reply);
processReplyBody();
return;
}
- if (doIcap(service) < 0) {
- /*
- * XXX Maybe instead of an error page we should
- * handle the reply normally (without ICAP).
- */
+ if (!startedIcap) {
+ // handle start failure for an essential ICAP service
ErrorState *err = errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
err->xerrno = errno;
errorAppendEntry(entry, err);
maybeReadData();
}
-void
+bool
FtpStateData::takeAdaptedHeaders(HttpReply *rep)
{
debug(11,5)("FtpStateData::takeAdaptedHeaders() called\n");
if (!entry->isAccepting()) {
debug(11,5)("\toops, entry is not Accepting!\n");
- icap->ownerAbort();
- return;
+ backstabAdapter();
+ return false;
}
assert (rep);
reply = HTTPMSGLOCK(rep);
debug(11,5)("FtpStateData::takeAdaptedHeaders() finished\n");
+ return true;
}
-void
+bool
FtpStateData::takeAdaptedBody(MemBuf *buf)
{
debug(11,5)("FtpStateData::takeAdaptedBody() called\n");
if (!entry->isAccepting()) {
debug(11,5)("\toops, entry is not Accepting!\n");
- icap->ownerAbort();
- return;
+ backstabAdapter();
+ return false;
}
storeAppend(entry, buf->content(), buf->contentSize());
buf->consume(buf->contentSize()); // consume everything written
+ return true;
}
void
-FtpStateData::doneAdapting()
+FtpStateData::finishAdapting()
{
debug(11,5)("FtpStateData::doneAdapting() called\n");
if (!entry->isAccepting()) {
debug(11,5)("\toops, entry is not Accepting!\n");
- icap->ownerAbort();
+ backstabAdapter();
} else {
- transactionForwardComplete();
+ transactionForwardComplete();
+ endAdapting();
}
-
- /*
- * ICAP is done, so we don't need this any more.
- */
- delete icap;
-
- cbdataReferenceDone(icap);
-
- if (ctrl.fd >= 0)
- comm_close(ctrl.fd);
- else
- delete this;
}
void
{
debug(11,5)("FtpStateData::abortAdapting() called\n");
- /*
- * ICAP has given up, we're done with it too
- */
- delete icap;
- cbdataReferenceDone(icap);
-
if (entry->isEmpty()) {
ErrorState *err;
err = errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
fwd->dontRetry(true);
}
+ endAdapting();
+}
+
+// internal helper to terminate adotation when called by the adapter
+void
+FtpStateData::backstabAdapter()
+{
+ debug(11,5)("HttpStateData::backstabAdapter() called for %p\n", icap);
+ assert(icap);
+ icap->ownerAbort();
+ endAdapting();
+}
+
+void
+FtpStateData::endAdapting()
+{
+ delete icap;
+ icap = NULL;
+
if (ctrl.fd >= 0)
comm_close(ctrl.fd);
else
delete this;
}
+
#endif
/*
- * $Id: http.cc,v 1.508 2006/10/01 17:26:34 adrian Exp $
+ * $Id: http.cc,v 1.509 2006/10/31 23:30:57 wessels Exp $
*
* DEBUG: section 11 Hypertext Transfer Protocol (HTTP)
* AUTHOR: Harvest Derived
{
icapAccessCheckPending = false;
- if (service == NULL) {
- // handle case where no service is selected;
+ const bool startedIcap = startIcap(service);
+
+ if (!startedIcap && (!service || service->bypass)) {
+ // handle ICAP start failure when no service was selected
+ // or where the selected service was optional
entry->replaceHttpReply(reply);
haveParsedReplyHeaders();
return;
}
- if (doIcap(service) < 0) {
- /*
- * XXX Maybe instead of an error page we should
- * handle the reply normally (without ICAP).
- */
+ if (!startedIcap) {
+ // handle start failure for an essential ICAP service
ErrorState *err = errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, orig_request);
err->xerrno = errno;
errorAppendEntry(entry, err);
maybeReadData();
}
-void
+bool
HttpStateData::takeAdaptedHeaders(HttpReply *rep)
{
debug(11,5)("HttpStateData::takeAdaptedHeaders() called\n");
if (!entry->isAccepting()) {
debug(11,5)("\toops, entry is not Accepting!\n");
- icap->ownerAbort();
- return;
+ backstabAdapter();
+ return false;
}
assert (rep);
haveParsedReplyHeaders();
debug(11,5)("HttpStateData::takeAdaptedHeaders() finished\n");
+ return true;
}
-void
+bool
HttpStateData::takeAdaptedBody(MemBuf *buf)
{
debug(11,5)("HttpStateData::takeAdaptedBody() called\n");
if (!entry->isAccepting()) {
debug(11,5)("\toops, entry is not Accepting!\n");
- icap->ownerAbort();
- return;
+ backstabAdapter();
+ return false;
}
entry->write(StoreIOBuffer(buf, currentOffset)); // write everything
currentOffset += buf->contentSize();
buf->consume(buf->contentSize()); // consume everything written
+ return true;
}
+// called when ICAP adaptation is about to finish successfully, destroys icap
+// must be called by the ICAP code
void
-HttpStateData::doneAdapting()
+HttpStateData::finishAdapting()
{
- debug(11,5)("HttpStateData::doneAdapting() called\n");
+ debug(11,5)("HttpStateData::finishAdapting() called by %p\n", icap);
- if (!entry->isAccepting()) {
+ if (!entry->isAccepting()) { // XXX: do we need this check here?
debug(11,5)("\toops, entry is not Accepting!\n");
- icap->ownerAbort();
+ backstabAdapter();
} else {
fwd->complete();
+ endAdapting();
}
-
- /*
- * ICAP is done, so we don't need this any more.
- */
- delete icap;
-
- cbdataReferenceDone(icap);
-
- if (fd >= 0)
- comm_close(fd);
- else
- httpStateFree(fd, this);
}
+// called when there was an ICAP error, destroys icap
+// must be called by the ICAP code
void
HttpStateData::abortAdapting()
{
- debug(11,5)("HttpStateData::abortAdapting() called\n");
-
- /*
- * ICAP has given up, we're done with it too
- */
- delete icap;
- cbdataReferenceDone(icap);
+ debug(11,5)("HttpStateData::abortAdapting() called by %p\n", icap);
if (entry->isEmpty()) {
ErrorState *err;
flags.do_next_read = 0;
}
- if (fd >= 0) {
+ endAdapting();
+}
+
+// internal helper to terminate adotation when called by the adapter
+void
+HttpStateData::backstabAdapter()
+{
+ debug(11,5)("HttpStateData::backstabAdapter() called for %p\n", icap);
+ assert(icap);
+ icap->ownerAbort();
+ endAdapting();
+}
+
+// internal helper to delete icap and close the HTTP connection
+void
+HttpStateData::endAdapting()
+{
+ debug(11,5)("HttpStateData::endAdapting() called, deleting %p\n", icap);
+
+ delete icap;
+ icap = NULL;
+
+ if (fd >= 0)
comm_close(fd);
- } else {
- httpStateFree(-1, this); // deletes this
- }
+ else
+ httpStateFree(fd, this); // deletes us
}
#endif
/*
- * $Id: http.h,v 1.25 2006/09/19 07:56:57 adrian Exp $
+ * $Id: http.h,v 1.26 2006/10/31 23:30:57 wessels Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
void readReply(size_t len, comm_err_t flag, int xerrno);
void maybeReadData();
int cacheableReply();
-#if ICAP_CLIENT
- void takeAdaptedHeaders(HttpReply *);
- void takeAdaptedBody(MemBuf *);
- void doneAdapting();
- void abortAdapting();
- void icapSpaceAvailable();
+#if ICAP_CLIENT
+ virtual bool takeAdaptedHeaders(HttpReply *);
+ virtual bool takeAdaptedBody(MemBuf *);
+ virtual void finishAdapting(); // deletes icap
+ virtual void abortAdapting(); // deletes icap
+ virtual void icapSpaceAvailable();
#endif
peer *_peer; /* peer request made to */
http_state_flags flags);
static bool decideIfWeDoRanges (HttpRequest * orig_request);
+#if ICAP_CLIENT
+ void backstabAdapter();
+ void endAdapting();
+#endif
+
private:
CBDATA_CLASS2(HttpStateData);
};