/*
- * $Id: HttpMsg.cc,v 1.39 2006/10/02 12:08:20 adrian Exp $
+ * $Id: HttpMsg.cc,v 1.40 2007/04/06 04:50:04 rousskov Exp $
*
* DEBUG: section 74 HTTP Message
* AUTHOR: Alex Rousskov
HttpMsg::~HttpMsg()
{
assert(lock_count == 0);
+ assert(!body_pipe);
}
HttpMsgParseState &operator++ (HttpMsgParseState &aState)
/*
- * $Id: HttpMsg.h,v 1.14 2006/10/02 01:34:18 adrian Exp $
+ * $Id: HttpMsg.h,v 1.15 2007/04/06 04:50:04 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
#include "typedefs.h"
#include "HttpHeader.h"
#include "HttpVersion.h"
+#include "BodyPipe.h"
// common parts of HttpRequest and HttpReply
HttpMsgParseState pstate; /* the current parsing state */
+ BodyPipe::Pointer body_pipe; // optional pipeline to receive message body
+
// returns true and sets hdr_sz on success
// returns false and sets *error to zero when needs more data
// returns false and sets *error to a positive http_status code on error
/*
- * $Id: HttpReply.cc,v 1.90 2006/10/31 23:30:56 wessels Exp $
+ * $Id: HttpReply.cc,v 1.91 2007/04/06 04:50:04 rousskov Exp $
*
* DEBUG: section 58 HTTP Reply (Response)
* AUTHOR: Alex Rousskov
void
HttpReply::clean()
{
+ // we used to assert that the pipe is NULL, but now the message only
+ // points to a pipe that is owned and initiated by another object.
+ body_pipe = NULL;
+
httpBodyClean(&body);
hdrCacheClean();
header.clean();
/*
- * $Id: HttpRequest.cc,v 1.70 2007/02/25 11:32:27 hno Exp $
+ * $Id: HttpRequest.cc,v 1.71 2007/04/06 04:50:04 rousskov Exp $
*
* DEBUG: section 73 HTTP Request
* AUTHOR: Duane Wessels
my_addr = no_addr;
my_port = 0;
client_port = 0;
- body_reader = NULL;
+ body_pipe = NULL;
// hier
errType = ERR_NONE;
peer_login = NULL; // not allocated/deallocated by this class
void
HttpRequest::clean()
{
- if (body_reader != NULL)
- fatal ("request being destroyed with body reader intact\n");
+ // we used to assert that the pipe is NULL, but now the request only
+ // points to a pipe that is owned and initiated by another object.
+ body_pipe = NULL;
if (auth_user_request) {
auth_user_request->unlock();
return destinationIPLookedUp_;
}
+request_flags
+request_flags::cloneAdaptationImmune() const
+{
+ // At the time of writing, all flags where either safe to copy after
+ // adaptation or were not set at the time of the adaptation. If there
+ // are flags that are different, they should be cleared in the clone.
+ return *this;
+}
+
+
const char *HttpRequest::packableURI(bool full_uri) const
{
if (full_uri)
/*
- * $Id: HttpRequest.h,v 1.24 2006/09/26 13:30:09 adrian Exp $
+ * $Id: HttpRequest.h,v 1.25 2007/04/06 04:50:04 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
#include "HttpMsg.h"
#include "client_side.h"
#include "HierarchyLogEntry.h"
-#include "BodyReader.h"
#include "HttpRequestMethod.h"
/* Http Request */
unsigned short client_port;
- BodyReader::Pointer body_reader;
-
HierarchyLogEntry hier;
err_type errType;
#include "squid.h"
-#include "ICAPModXact.h"
#include "ICAPClient.h"
-#include "http.h"
void ICAPInitModule()
{
- /*
- * ICAP's MsgPipe buffer needs to be at least as large
- * as the HTTP read buffer. Otherwise HTTP may take
- * data from the network that won't fit into the MsgPipe,
- * which leads to a runtime assertion.
- */
- assert(ICAP::MsgPipeBufSizeMax >= SQUID_TCP_SO_RCVBUF);
+ debugs(93,2, "ICAP Client module enabled.");
}
void ICAPCleanModule()
-{}
-
-// initialize ICAP-specific ends of message pipes
-void ICAPInitXaction(ICAPServiceRep::Pointer service, MsgPipe::Pointer virgin, MsgPipe::Pointer adapted)
-{
- ICAPModXact::Pointer x = new ICAPModXact;
- debugs(93,5, "ICAPInitXaction: " << x.getRaw());
- x->init(service, virgin, adapted, x);
- // if we want to do something to the transaction after it is done,
- // we need to keep a pointer to it
-}
-
-// declared in ICAPModXact.h (ick?)
-void ICAPNoteXactionDone(ICAPModXact::Pointer x)
{
- // nothing to be done here?
- // refcounting will delete the transaction
- // as soon as the last pointer to it is gone
- debugs(93,5, "ICAPNoteXactionDone: " << x.getRaw());
}
/*
- * $Id: ICAPClient.h,v 1.3 2005/12/22 22:26:31 wessels Exp $
+ * $Id: ICAPClient.h,v 1.4 2007/04/06 04:50:07 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
#ifndef SQUID_ICAPCLIENT_H
#define SQUID_ICAPCLIENT_H
-#include "MsgPipe.h" // TODO: move; needed for ICAPInitXaction()
-#include "ICAPServiceRep.h" // TODO: move; needed for ICAPInitXaction()
-
// ICAP-related things needed by code unaware of ICAP internals.
extern void ICAPInitModule();
extern void ICAPCleanModule();
-// let ICAP initialize ICAP-specific ends of message pipes
-
-class MsgPipe;
-extern void ICAPInitXaction(ICAPServiceRep::Pointer, MsgPipe::Pointer virgin, MsgPipe::Pointer adapted);
-
#endif /* SQUID_ICAPCLIENT_H */
+++ /dev/null
-#include "squid.h"
-#include "client_side_request.h"
-#include "ClientRequestContext.h"
-#include "MsgPipeData.h"
-#include "HttpRequest.h"
-#include "ICAPClientReqmodPrecache.h"
-#include "ICAPServiceRep.h"
-#include "ICAPClient.h"
-
-CBDATA_CLASS_INIT(ICAPClientReqmodPrecache);
-
-ICAPClientReqmodPrecache::ICAPClientReqmodPrecache(ICAPServiceRep::Pointer aService):
- ICAPClientVector(aService, "ICAPClientReqmodPrecache"), http(NULL)
-{
-}
-
-void ICAPClientReqmodPrecache::startReqMod(ClientHttpRequest *aHttp, HttpRequest *request)
-{
- http = cbdataReference(aHttp);
- startMod(http, NULL, request);
-}
-
-void ICAPClientReqmodPrecache::tellSpaceAvailable() {
- http->icapSpaceAvailable();
-}
-
-// ICAP client starts sending adapted response
-// ICAP client has received new HTTP headers (if any) at this point
-void ICAPClientReqmodPrecache::noteSourceStart(MsgPipe *p)
-{
- debug(93,3)("ICAPClientReqmodPrecache::noteSourceStart() called\n");
- /*
- * If adapted->data->header is NULL then the ICAP response did
- * not have a req/res-hdr section. Send the NULL pointer to
- * tell the other side to use the original request/response
- * headers.
- */
- HttpRequest *req = dynamic_cast<HttpRequest*>(adapted->data->header);
-
- if (req && req->content_length > 0) {
- assert(req->body_reader == NULL);
- req->body_reader = new BodyReader(req->content_length, readBody, abortBody, kickBody, this);
- }
-
- http->takeAdaptedHeaders(adapted->data->header);
- noteSourceProgress(p);
-}
-
-/*
- * This is where we receive a notification from the other
- * side of the MsgPipe that new adapted data is available.
- * We, in turn, tell whoever is reading from the request's
- * body_reader about the new data.
- */
-void ICAPClientReqmodPrecache::noteSourceProgress(MsgPipe *p)
-{
- debug(93,3)("ICAPClientReqmodPrecache::noteSourceProgress() called\n");
- //tell ClientHttpRequest to store a fresh portion of the adapted response
-
- if (p->data->body->hasContent()) {
- /*
- * NOTE: req will be NULL if this is a "request satisfaction"
- * ICAP reply. In other words, the ICAP REQMOD reply may
- * contain an HTTP response, in which case we'll have a body, but
- * adapted->data->header will be an HttpReply, not an HttpRequest.
- */
- HttpRequest *req = dynamic_cast<HttpRequest*>(adapted->data->header);
-
- if (req) {
- 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);
- }
- }
-}
-
-void ICAPClientReqmodPrecache::tellDoneAdapting()
-{
- debug(93,3)("ICAPClientReqmodPrecache::tellDoneAdapting() called\n");
- //tell ClientHttpRequest that we expect no more response data
- http->doneAdapting(); // does not delete us (yet?)
- stop(notifyNone);
- // we should be eventually deleted by owner in ~ClientHttpRequest()
-}
-
-void ICAPClientReqmodPrecache::tellAbortAdapting()
-{
- 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)
-{
- /*
- * 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.
- */
-
- // 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?
-
- // 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?
-
- ICAPClientVector::clean(notify, false);
-}
-
-/*
- * Something that needs to read the adapated request body
- * calls this function, via the BodyReader class. We copy
- * the body data from our bodybuf object to the BodyReader
- * MemBuf, which was passed as a reference to this function.
- */
-size_t
-ICAPClientReqmodPrecache::readBody(void *data, MemBuf &mb, size_t size)
-{
- ICAPClientReqmodPrecache *icap = static_cast<ICAPClientReqmodPrecache *>(data);
- assert(icap != NULL);
- assert(icap->adapted != NULL);
- assert(icap->adapted->data != NULL);
- MemBuf *bodybuf = icap->adapted->data->body;
- assert(bodybuf != NULL);
- 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(93,3,HERE << "readBody actual size " << size);
-
- assert(size);
-
- mb.append(bodybuf->content(), size);
-
- bodybuf->consume(size);
-
- return size;
-}
-
-void
-ICAPClientReqmodPrecache::abortBody(void *data, size_t remaining)
-{
- if (remaining >= 0) {
- debugs(93,1,HERE << "ICAPClientReqmodPrecache::abortBody size " << remaining);
- // more?
- }
-
- ICAPClientReqmodPrecache *icap = static_cast<ICAPClientReqmodPrecache *>(data);
- icap->stop(notifyIcap);
-}
-
-/*
- * Restart reading the adapted response from the ICAP server in case
- * the body buffer became full and we stopped reading.
- */
-void
-ICAPClientReqmodPrecache::kickBody(void *data)
-{
- debugs(93,3,HERE << "ICAPClientReqmodPrecache::kickBody");
- ICAPClientReqmodPrecache *icap = static_cast<ICAPClientReqmodPrecache *>(data);
- assert(icap->adapted != NULL);
- icap->adapted->sendSinkNeed();
-}
+++ /dev/null
-
-/*
- * $Id: ICAPClientReqmodPrecache.h,v 1.4 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_ICAPCLIENTREQMODPRECACHE_H
-#define SQUID_ICAPCLIENTREQMODPRECACHE_H
-
-#include "ICAPClientVector.h"
-
-/*
- * ICAPClientReqmodPrecache implements the ICAP client-side pre-cache
- * vectoring point using ICAPClientVector as a parent.
- * ClientHttpRequest is the Owner of this vectoring point.
- */
-
-class ClientRequestContext;
-
-class ICAPClientReqmodPrecache: public ICAPClientVector
-{
-
-public:
- ICAPClientReqmodPrecache(ICAPServiceRep::Pointer);
-
- // synchronous calls called by ClientHttpRequest
- void startReqMod(ClientHttpRequest *, HttpRequest *);
-
- // pipe source methods; called by ICAP while receiving the virgin message
-
-
- // pipe sink methods; called by ICAP while sending the adapted message
- virtual void noteSourceStart(MsgPipe *p);
- virtual void noteSourceProgress(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:
- ClientHttpRequest *http;
- BodyReader::Pointer body_reader;
-
-private:
- // 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 */
+++ /dev/null
-#include "squid.h"
-#include "http.h"
-#include "MsgPipeData.h"
-#include "HttpRequest.h"
-#include "HttpReply.h"
-#include "ICAPClientRespmodPrecache.h"
-#include "ICAPClient.h"
-#include "ICAPServiceRep.h"
-
-CBDATA_CLASS_INIT(ICAPClientRespmodPrecache);
-
-ICAPClientRespmodPrecache::ICAPClientRespmodPrecache(ICAPServiceRep::Pointer aService):
- ICAPClientVector(aService, "ICAPClientRespmodPrecache"), serverState(NULL)
-{
-}
-
-void ICAPClientRespmodPrecache::startRespMod(ServerStateData *aServerState, HttpRequest *request, HttpReply *reply)
-{
- 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,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.
- */
- assert(reply); // check that ICAP xaction created the right object
- assert(reply == adapted->data->header);
-
- /*
- * Examine the HTTP reply headers to find out if there is an associated
- * body. We should probably check the ICAP Encapsulated header values
- * as well.
- */
- ssize_t dummy;
- bool expect_body = reply->expectingBody(virgin->data->cause->method, dummy);
-
- if (!serverState->takeAdaptedHeaders(reply)) // deletes us
- return;
-
- if (expect_body)
- noteSourceProgress(p);
- else
- noteSourceFinish(p);
-}
-
-// ICAP client sends more data
-void ICAPClientRespmodPrecache::noteSourceProgress(MsgPipe *p)
-{
- 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()) {
- if (!serverState->takeAdaptedBody(p->data->body))
- return;
-
- // 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();
- }
-}
-
-void
-ICAPClientRespmodPrecache::tellSpaceAvailable()
-{
- serverState->icapSpaceAvailable();
-}
-
-void
-ICAPClientRespmodPrecache::tellDoneAdapting()
-{
- serverState->finishAdapting(); // deletes us
-}
-
-void
-ICAPClientRespmodPrecache::tellAbortAdapting()
-{
- debug(93,3)("ICAPClientReqmodPrecache::tellAbortAdapting() called\n");
- // tell ClientHttpRequest that we are aborting ICAP processing prematurely
- serverState->abortAdapting(); // deletes us
-}
-
+++ /dev/null
-
-/*
- * $Id: ICAPClientRespmodPrecache.h,v 1.4 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_ICAPCLIENTRESPMODPRECACHE_H
-#define SQUID_ICAPCLIENTRESPMODPRECACHE_H
-
-#include "ICAPClientVector.h"
-
-/*
- * ICAPClientRespmodPrecache implements the server-side pre-cache ICAP
- * vectoring point using ICAPClientVector as a parent.
- * ServerStateData is the Owner of this vectoring point.
- */
-
-class ServerStateData;
-
-class ICAPClientRespmodPrecache: public ICAPClientVector
-{
-
-public:
- ICAPClientRespmodPrecache(ICAPServiceRep::Pointer);
-
- // synchronous calls called by ServerStateData
- void startRespMod(ServerStateData *anServerState, HttpRequest *request, HttpReply *reply);
-
- // pipe source methods; called by ICAP while receiving the virgin message
-
- // pipe sink methods; called by ICAP while sending the adapted message
- virtual void noteSourceStart(MsgPipe *p);
- virtual void noteSourceProgress(MsgPipe *p);
-
-protected:
- virtual void tellSpaceAvailable();
- virtual void tellDoneAdapting(); // deletes us
- virtual void tellAbortAdapting(); // deletes us
-
-public:
- ServerStateData *serverState;
-
-private:
- CBDATA_CLASS2(ICAPClientRespmodPrecache);
-};
-
-#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.12 2006/10/31 23:30:58 wessels Exp $
+ * $Id: ICAPConfig.cc,v 1.13 2007/04/06 04:50:07 rousskov Exp $
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
* ----------------------------------------------------------
Vector<ICAPServiceRep::Pointer>::iterator iter = services.begin();
while (iter != services.end()) {
- if (iter->getRaw()->key == key)
+ if ((*iter)->key == key)
return *iter;
++iter;
*/
ICAPClass *c = *ci;
ICAPServiceRep::Pointer service = findBestService(c, false);
- if (service.getRaw()) {
+ if (service != NULL) {
debug(93,3)("ICAPAccessCheck::check: class '%s' has candidate service '%s'\n", c->key.buf(), service->key.buf());
candidateClasses += c->key;
}
debug(93,3)("ICAPAccessCheck::do_callback matchedClass = %s\n", matchedClass.buf());
}
- ICAPClass *theClass = TheICAPConfig.findClass(matchedClass);
-
- if (theClass == NULL) {
- callback(NULL, callback_data);
- return;
- }
-
- matchedClass.clean();
-
void *validated_cbdata;
-
if (!cbdataReferenceValidDone(callback_data, &validated_cbdata)) {
debugs(93,3,HERE << "do_callback: callback_data became invalid, skipping");
return;
}
- const ICAPServiceRep::Pointer service = findBestService(theClass, true);
- if (!service)
- callback(NULL, validated_cbdata);
- else
- callback(service, validated_cbdata);
+ ICAPServiceRep::Pointer service = NULL;
+ if (ICAPClass *c = TheICAPConfig.findClass(matchedClass)) {
+ service = findBestService(c, true);
+ if (service != NULL)
+ debugs(93,3,HERE << "do_callback: with service " << service->uri);
+ else
+ debugs(93,3,HERE << "do_callback: no " << matchedClass << " service");
+ } else {
+ debugs(93,3,HERE << "do_callback: no " << matchedClass << " class");
+ }
+
+ callback(service, validated_cbdata);
}
ICAPServiceRep::Pointer
return service;
}
- if (secondBest.getRaw()) {
+ if (secondBest != NULL) {
what = "down ";
debugs(93,5,HERE << "found first matching " <<
what << "service in class " << c->key <<
/*
- * $Id: ICAPConfig.h,v 1.10 2006/10/31 23:30:58 wessels Exp $
+ * $Id: ICAPConfig.h,v 1.11 2007/04/06 04:50:07 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
class ConfigParser;
-class ICAPConfig;
-
class ICAPClass
{
int send_client_ip;
int send_client_username;
int reuse_connections;
+ int service_failure_limit;
+ int service_revival_delay;
+ char* client_username_header;
+ int client_username_encode;
Vector<ICAPServiceRep::Pointer> services;
Vector<ICAPClass*> classes;
};
+extern ICAPConfig TheICAPConfig;
+
#endif /* SQUID_ICAPCONFIG_H */
/*
- * $Id: ICAPElements.h,v 1.3 2005/12/22 22:26:31 wessels Exp $
+ * $Id: ICAPElements.h,v 1.4 2007/04/06 04:50:07 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
typedef enum { methodNone, methodReqmod, methodRespmod, methodOptions } Method;
typedef enum { pointNone, pointPreCache, pointPostCache } VectPoint;
- // recommended initial size and max capacity for MsgPipe buffer
- enum { MsgPipeBufSizeMin = (4*1024), MsgPipeBufSizeMax = SQUID_TCP_SO_RCVBUF };
-
static const char *crlf;
static const char *methodStr(ICAP::Method);
static const char *vectPointStr(ICAP::VectPoint);
/*
- * $Id: MsgPipeData.h,v 1.8 2006/10/31 23:30:58 wessels Exp $
+ * $Id: ICAPInOut.h,v 1.1 2007/04/06 04:50:07 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
*
*/
-#ifndef SQUID_MSGPIPEDATA_H
-#define SQUID_MSGPIPEDATA_H
+#ifndef SQUID_ICAPINOUT_H
+#define SQUID_ICAPINOUT_H
#include "HttpMsg.h"
#include "HttpRequest.h"
#include "HttpReply.h"
-#include "MemBuf.h"
-// MsgPipeData contains information about the HTTP message being sent
-// from the pipe source to the sink. Since the entire message body may be
-// large, only partial information about the body is kept. For HTTP
-// responses, request header information is also available as metadata.
+// IcapInOut manages a pointer to the HTTP message being worked on.
+// For HTTP responses, request header information is also available
+// as the "cause". ICAP transactions use this class to store virgin
+// and adapted HTTP messages.
-class HttpRequest;
-
-class MsgPipeData
+class ICAPInOut
{
public:
- MsgPipeData(): header(0), body(0), cause(0) {}
+ typedef HttpMsg Header;
- ~MsgPipeData()
+ ICAPInOut(): header(0), cause(0) {}
+
+ ~ICAPInOut()
{
HTTPMSGUNLOCK(cause);
HTTPMSGUNLOCK(header);
-
- if (body) {
- body->clean();
- delete body;
- }
}
void setCause(HttpRequest *r)
}
}
- void setHeader(HttpMsg *msg)
+ void setHeader(Header *h)
{
HTTPMSGUNLOCK(header);
- header = HTTPMSGLOCK(msg);
+ header = HTTPMSGLOCK(h);
+ body_pipe = header->body_pipe;
}
public:
- typedef HttpMsg Header;
- typedef MemBuf Body;
-
- // message being piped
+ // virgin or adapted message being worked on
Header *header; // parsed HTTP status/request line and headers
- Body *body; // a buffer for decoded HTTP body piping
- // HTTP request header for piped responses (the cause of the response)
+ // HTTP request header for HTTP responses (the cause of the response)
HttpRequest *cause;
+ // Copy of header->body_pipe, in case somebody moves the original.
+ BodyPipe::Pointer body_pipe;
};
-#endif /* SQUID_MSGPIPEDATA_H */
+// TODO: s/Header/Message/i ?
+
+#endif /* SQUID_ICAPINOUT_H */
--- /dev/null
+// XXX: remove me!
/*
- * $Id: MsgPipeSink.h,v 1.2 2005/11/21 23:46:27 wessels Exp $
+ * $Id: ICAPInitiator.h,v 1.1 2007/04/06 04:50:07 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
*
*/
-#ifndef SQUID_MSGPIPESINK_H
-#define SQUID_MSGPIPESINK_H
+#ifndef SQUID_ICAPINITIATOR_H
+#define SQUID_ICAPINITIATOR_H
-#include "MsgPipeEnd.h"
+#include "AsyncCall.h"
-// MsgPipeSink is an interface for the recepient of a given message
-// over a given message pipe. Use MsgPipe to call sink methods.
+/*
+ * The ICAP Initiator is an ICAP vectoring point that initates ICAP
+ * transactions. This interface exists to allow ICAP transactions to
+ * signal their initiators that they are finished or aborted.
+ */
-class MsgPipe;
+class ICAPXaction;
-class MsgPipeSink: public MsgPipeEnd
+class ICAPInitiator
{
-
public:
- virtual void noteSourceStart(MsgPipe *p) = 0;
- virtual void noteSourceProgress(MsgPipe *p) = 0;
- virtual void noteSourceFinish(MsgPipe *p) = 0;
- virtual void noteSourceAbort(MsgPipe *p) = 0;
+ virtual ~ICAPInitiator() {}
+
+ virtual void noteIcapHeadersAdapted() = 0;
+ virtual void noteIcapHeadersAborted() = 0;
- virtual const char *kind() const { return "sink"; }
+ AsyncCallWrapper(93,4, ICAPInitiator, noteIcapHeadersAdapted);
+ AsyncCallWrapper(93,3, ICAPInitiator, noteIcapHeadersAborted);
};
-#endif /* SQUID_MSGPIPESINK_H */
+#endif /* SQUID_ICAPINITIATOR_H */
#include "squid.h"
#include "comm.h"
-#include "MsgPipe.h"
-#include "MsgPipeData.h"
+#include "HttpMsg.h"
#include "HttpRequest.h"
#include "HttpReply.h"
#include "ICAPServiceRep.h"
+#include "ICAPInitiator.h"
#include "ICAPModXact.h"
#include "ICAPClient.h"
#include "ChunkedCodingParser.h"
// HTTP| --> receive --> encode --> write --> |network
// end | <-- send <-- parse <-- read <-- |end
-// TODO: doneSending()/doneReceving() data members should probably be in sync
-// with this->adapted/virgin pointers. Make adapted/virgin methods?
-
// TODO: replace gotEncapsulated() with something faster; we call it often
CBDATA_CLASS_INIT(ICAPModXact);
-static const size_t TheBackupLimit = ICAP::MsgPipeBufSizeMax;
+static const size_t TheBackupLimit = BodyPipe::MaxCapacity;
extern ICAPConfig TheICAPConfig;
memset(this, sizeof(*this), 0);
}
-ICAPModXact::ICAPModXact(): ICAPXaction("ICAPModXact"),
- self(NULL), virgin(NULL), adapted(NULL),
- icapReply(NULL), virginConsumed(0),
- bodyParser(NULL)
-{}
-
-void ICAPModXact::init(ICAPServiceRep::Pointer &aService, MsgPipe::Pointer &aVirgin, MsgPipe::Pointer &anAdapted, Pointer &aSelf)
+ICAPModXact::ICAPModXact(ICAPInitiator *anInitiator, HttpMsg *virginHeader,
+ HttpRequest *virginCause, ICAPServiceRep::Pointer &aService):
+ ICAPXaction("ICAPModXact"),
+ initiator(cbdataReference(anInitiator)),
+ icapReply(NULL),
+ virginConsumed(0),
+ bodyParser(NULL)
{
- assert(!self.getRaw() && !virgin.getRaw() && !adapted.getRaw());
- assert(aSelf.getRaw() && aVirgin.getRaw() && anAdapted.getRaw());
+ assert(virginHeader);
- self = aSelf;
service(aService);
- virgin = aVirgin;
- adapted = anAdapted;
-
- // receiving end
- virgin->sink = this; // should be 'self' and refcounted
- // virgin pipe data is initiated by the source
-
- // sending end
- adapted->source = this; // should be 'self' and refcounted
- adapted->data = new MsgPipeData;
+ virgin.setHeader(virginHeader); // sets virgin.body_pipe if needed
+ virgin.setCause(virginCause); // may be NULL
- adapted->data->body = new MemBuf; // XXX: make body a non-pointer?
- adapted->data->body->init(ICAP::MsgPipeBufSizeMin, ICAP::MsgPipeBufSizeMax);
- // headers are initialized when we parse them
+ // adapted header and body are initialized when we parse them
// writing and reading ends are handled by ICAPXaction
icapReply = new HttpReply;
icapReply->protoPrefix = "ICAP/"; // TODO: make an IcapReply class?
- // XXX: make sure stop() cleans all buffers
+ debugs(93,7, "ICAPModXact initialized." << status());
}
-// HTTP side starts sending virgin data
-void ICAPModXact::noteSourceStart(MsgPipe *p)
+// initiator wants us to start
+void ICAPModXact::start()
{
- ICAPXaction_Enter(noteSourceStart);
+ ICAPXaction_Enter(start);
- // make sure TheBackupLimit is in-sync with the buffer size
- Must(TheBackupLimit <= static_cast<size_t>(virgin->data->body->max_capacity));
+ ICAPXaction::start();
estimateVirginBody(); // before virgin disappears!
// XXX: but this has to be here to catch other errors. Thus, if
// commConnectStart in startWriting fails, we may get here
//_after_ the object got destroyed. Somebody please fix commConnectStart!
+ // XXX: Is the above comment still valid?
ICAPXaction_Exit();
}
void ICAPModXact::waitForService()
{
Must(!state.serviceWaiting);
- debugs(93, 7, "ICAPModXact will wait for the ICAP service " << status());
+ debugs(93, 7, "ICAPModXact will wait for the ICAP service" << status());
state.serviceWaiting = true;
service().callWhenReady(&ICAPModXact_noteServiceReady, this);
}
requestBuf.init();
makeRequestHeaders(requestBuf);
- debugs(93, 9, "ICAPModXact ICAP status " << status() << " will write:\n" <<
+ debugs(93, 9, "ICAPModXact ICAP will write" << status() << ":\n" <<
(requestBuf.terminate(), requestBuf.content()));
// write headers
state.writing = State::writingHeaders;
scheduleWrite(requestBuf);
- virgin->sendSinkNeed();
}
void ICAPModXact::handleCommWrote(size_t sz)
{
Must(state.writing == State::writingHeaders);
- if (virginBody.expected()) {
- state.writing = preview.enabled() ?
- State::writingPreview : State::writingPrime;
- virginWriteClaim.protectAll();
- writeMore();
- } else {
+ // determine next step
+ if (preview.enabled())
+ state.writing = preview.done() ? State::writingPaused : State::writingPreview;
+ else
+ if (virginBody.expected())
+ state.writing = State::writingPrime;
+ else {
stopWriting(true);
+ return;
}
+
+ writeMore();
}
void ICAPModXact::writeMore()
{
+ debugs(93, 5, HERE << "checking whether to write more" << status());
+
if (writer) // already writing something
return;
return;
case State::writingPreview:
- writePriviewBody();
+ writePreviewBody();
return;
case State::writingPrime:
}
}
-void ICAPModXact::writePriviewBody()
+void ICAPModXact::writePreviewBody()
{
- debugs(93, 8, "ICAPModXact will write Preview body " << status());
+ debugs(93, 8, HERE << "will write Preview body from " <<
+ virgin.body_pipe << status());
Must(state.writing == State::writingPreview);
+ Must(virgin.body_pipe != NULL);
- MsgPipeData::Body *body = virgin->data->body;
- const size_t size = XMIN(preview.debt(), (size_t)body->contentSize());
+ const size_t sizeMax = (size_t)virgin.body_pipe->buf().contentSize();
+ const size_t size = XMIN(preview.debt(), sizeMax);
writeSomeBody("preview body", size);
// change state once preview is written
if (preview.done()) {
- debugs(93, 7, "ICAPModXact wrote entire Preview body " << status());
+ debugs(93, 7, "ICAPModXact wrote entire Preview body" << status());
if (preview.ieof())
stopWriting(true);
void ICAPModXact::writePrimeBody()
{
Must(state.writing == State::writingPrime);
- Must(virginWriteClaim.active());
+ Must(virginBodyWriting.active());
- MsgPipeData::Body *body = virgin->data->body;
- const size_t size = body->contentSize();
+ const size_t size = (size_t)virgin.body_pipe->buf().contentSize();
writeSomeBody("prime virgin body", size);
- if (state.doneReceiving && claimSize(virginWriteClaim) <= 0) {
- debugs(93, 5, HERE << "state.doneReceiving is set and wrote all");
+ if (virginBodyEndReached(virginBodyWriting)) {
+ debugs(93, 5, HERE << "wrote entire body");
stopWriting(true);
}
}
void ICAPModXact::writeSomeBody(const char *label, size_t size)
{
Must(!writer && state.writing < state.writingAlmostDone);
+ Must(virgin.body_pipe != NULL);
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 writableSize = claimSize(virginWriteClaim);
+ const size_t writableSize = virginContentSize(virginBodyWriting);
const size_t chunkSize = XMIN(writableSize, size);
if (chunkSize) {
debugs(93, 7, HERE << "will write " << chunkSize <<
"-byte chunk of " << label);
+
+ openChunk(writeBuf, chunkSize, false);
+ writeBuf.append(virginContentData(virginBodyWriting), chunkSize);
+ closeChunk(writeBuf);
+
+ virginBodyWriting.progress(chunkSize);
+ virginConsume();
} else {
debugs(93, 7, "ICAPModXact has no writable " << label << " content");
}
- moveRequestChunk(writeBuf, chunkSize);
-
- const bool lastChunk =
- (state.writing == State::writingPreview && preview.done()) ||
- (state.doneReceiving && claimSize(virginWriteClaim) <= 0);
+ const bool wroteEof = virginBodyEndReached(virginBodyWriting);
+ bool lastChunk = wroteEof;
+ if (state.writing == State::writingPreview) {
+ preview.wrote(chunkSize, wroteEof); // even if wrote nothing
+ lastChunk = lastChunk || preview.done();
+ }
- if (lastChunk && virginBody.expected()) {
+ if (lastChunk) {
debugs(93, 8, HERE << "will write last-chunk of " << label);
addLastRequestChunk(writeBuf);
}
}
}
-void ICAPModXact::moveRequestChunk(MemBuf &buf, size_t chunkSize)
-{
- if (chunkSize > 0) {
- openChunk(buf, chunkSize, false);
- buf.append(claimContent(virginWriteClaim), chunkSize);
- closeChunk(buf);
-
- virginWriteClaim.release(chunkSize);
- virginConsume();
- }
-
- 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)
{
const bool ieof = state.writing == State::writingPreview && preview.ieof();
buf.append(ICAP::crlf, 2); // chunk-terminating CRLF
}
-size_t ICAPModXact::claimSize(const MemBufClaim &claim) const
+// did the activity reached the end of the virgin body?
+bool ICAPModXact::virginBodyEndReached(const VirginBodyAct &act) const
+{
+ return
+ !act.active() || // did all (assuming it was originally planned)
+ !virgin.body_pipe->expectMoreAfter(act.offset()); // wont have more
+}
+
+// the size of buffered virgin body data available for the specified activity
+// if this size is zero, we may be done or may be waiting for more data
+size_t ICAPModXact::virginContentSize(const VirginBodyAct &act) const
{
- Must(claim.active());
- const size_t start = claim.offset();
- const size_t end = virginConsumed + virgin->data->body->contentSize();
+ Must(act.active());
+ // asbolute start of unprocessed data
+ const size_t start = act.offset();
+ // absolute end of buffered data
+ const size_t end = virginConsumed + virgin.body_pipe->buf().contentSize();
Must(virginConsumed <= start && start <= end);
return end - start;
}
-const char *ICAPModXact::claimContent(const MemBufClaim &claim) const
+// pointer to buffered virgin body data available for the specified activity
+const char *ICAPModXact::virginContentData(const VirginBodyAct &act) const
{
- Must(claim.active());
- const size_t start = claim.offset();
+ Must(act.active());
+ const size_t start = act.offset();
Must(virginConsumed <= start);
- return virgin->data->body->content() + (start - virginConsumed);
+ return virgin.body_pipe->buf().content() + (start-virginConsumed);
}
void ICAPModXact::virginConsume()
{
- MemBuf &buf = *virgin->data->body;
- const size_t have = static_cast<size_t>(buf.contentSize());
+ if (!virgin.body_pipe)
+ return;
+
+ BodyPipe &bp = *virgin.body_pipe;
+ const size_t have = static_cast<size_t>(bp.buf().contentSize());
const size_t end = virginConsumed + have;
size_t offset = end;
- if (virginWriteClaim.active())
- offset = XMIN(virginWriteClaim.offset(), offset);
+ if (virginBodyWriting.active())
+ offset = XMIN(virginBodyWriting.offset(), offset);
- if (virginSendClaim.active())
- offset = XMIN(virginSendClaim.offset(), offset);
+ if (virginBodySending.active())
+ offset = XMIN(virginBodySending.offset(), offset);
Must(virginConsumed <= offset && offset <= end);
if (const size_t size = offset - virginConsumed) {
debugs(93, 8, HERE << "consuming " << size << " out of " << have <<
" virgin body bytes");
- buf.consume(size);
+ bp.consume(size);
virginConsumed += size;
-
- if (!state.doneReceiving)
- virgin->sendSinkNeed();
}
}
if (writer) {
if (nicely) {
- debugs(93, 7, HERE << "will wait for the last write " << status());
+ debugs(93, 7, HERE << "will wait for the last write" << status());
state.writing = State::writingAlmostDone; // may already be set
+ checkConsuming();
return;
}
- debugs(93, 2, HERE << "will NOT wait for the last write " << status());
+ debugs(93, 2, HERE << "will NOT wait for the last write" << status());
// 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.
+ // call at any time, usually in the middle of the destruction sequence!
+ // Somebody should add comm_remove_write_handler() to comm API.
reuseConnection = false;
}
- debugs(93, 7, HERE << "will no longer write " << status());
+ debugs(93, 7, HERE << "will no longer write" << status());
state.writing = State::writingReallyDone;
- virginWriteClaim.disable();
-
- virginConsume();
+ if (virginBodyWriting.active()) {
+ virginBodyWriting.disable();
+ virginConsume();
+ }
}
void ICAPModXact::stopBackup()
{
- if (!virginSendClaim.active())
+ if (!virginBodySending.active())
return;
- debugs(93, 7, "ICAPModXact will no longer backup " << status());
-
- virginSendClaim.disable();
-
+ debugs(93, 7, "ICAPModXact will no longer backup" << status());
+ virginBodySending.disable();
virginConsume();
}
bool ICAPModXact::doneAll() const
{
return ICAPXaction::doneAll() && !state.serviceWaiting &&
- state.doneReceiving && doneSending() &&
+ doneSending() &&
doneReading() && state.doneWriting();
}
{
Must(connection >= 0);
Must(!reader);
- Must(adapted.getRaw());
- Must(adapted->data);
- Must(adapted->data->body);
+ Must(!adapted.header);
+ Must(!adapted.body_pipe);
// we use the same buffer for headers and body and then consume headers
readMore();
}
// do not fill readBuf if we have no space to store the result
- if (!adapted->data->body->hasPotentialSpace()) {
- debugs(93,3,HERE << "not reading because ICAP reply buffer is full");
+ if (adapted.body_pipe != NULL &&
+ !adapted.body_pipe->buf().hasPotentialSpace()) {
+ debugs(93,3,HERE << "not reading because ICAP reply pipe is full");
return;
}
void ICAPModXact::echoMore()
{
Must(state.sending == State::sendingVirgin);
- Must(virginSendClaim.active());
-
- MemBuf &from = *virgin->data->body;
- MemBuf &to = *adapted->data->body;
-
- const size_t sizeMax = claimSize(virginSendClaim);
- const size_t size = XMIN(static_cast<size_t>(to.potentialSpaceSize()),
- sizeMax);
- debugs(93, 5, "ICAPModXact echos " << size << " out of " << sizeMax <<
+ Must(adapted.body_pipe != NULL);
+ Must(virginBodySending.active());
+
+ const size_t sizeMax = virginContentSize(virginBodySending);
+ debugs(93,5, HERE << "will echo up to " << sizeMax << " bytes from " <<
+ virgin.body_pipe->status());
+ debugs(93,5, HERE << "will echo up to " << sizeMax << " bytes to " <<
+ adapted.body_pipe->status());
+
+ if (sizeMax > 0) {
+ const size_t size = adapted.body_pipe->putMoreData(virginContentData(virginBodySending), sizeMax);
+ debugs(93,5, HERE << "echoed " << size << " out of " << sizeMax <<
" bytes");
-
- if (size > 0) {
- to.append(claimContent(virginSendClaim), size);
- virginSendClaim.release(size);
+ virginBodySending.progress(size);
virginConsume();
- adapted->sendSourceProgress();
}
- if (state.doneReceiving && claimSize(virginSendClaim) <= 0) {
- debugs(93, 5, "ICAPModXact echoed all " << status());
+ if (virginBodyEndReached(virginBodySending)) {
+ debugs(93, 5, "ICAPModXact echoed all" << status());
stopSending(true);
} else {
- debugs(93, 5, "ICAPModXact has " << from.contentSize() << " bytes " <<
- "and expects more to echo " << status());
- virgin->sendSinkNeed(); // TODO: timeout if sink is broken
+ debugs(93, 5, "ICAPModXact has " <<
+ virgin.body_pipe->buf().contentSize() << " bytes " <<
+ "and expects more to echo" << status());
+ // TODO: timeout if virgin or adapted pipes are broken
}
}
bool ICAPModXact::doneSending() const
{
- Must((state.sending == State::sendingDone) == (!adapted));
return state.sending == State::sendingDone;
}
return;
if (state.sending != State::sendingUndecided) {
- debugs(93, 7, "ICAPModXact will no longer send " << status());
-
- if (nicely)
- adapted->sendSourceFinish();
- else
- adapted->sendSourceAbort();
+ debugs(93, 7, "ICAPModXact will no longer send" << status());
+ if (adapted.body_pipe != NULL) {
+ virginBodySending.disable();
+ // we may leave debts if we were echoing and the virgin
+ // body_pipe got exhausted before we echoed all planned bytes
+ const bool leftDebts = adapted.body_pipe->needsMoreData();
+ stopProducingFor(adapted.body_pipe, nicely && !leftDebts);
+ }
} else {
- debugs(93, 7, "ICAPModXact will not start sending " << status());
- adapted->sendSourceAbort(); // or the sink may wait forever
+ debugs(93, 7, "ICAPModXact will not start sending" << status());
+ Must(!adapted.body_pipe);
}
state.sending = State::sendingDone;
-
- adapted = NULL; // refcounted
+ checkConsuming();
}
-void ICAPModXact::stopReceiving()
+// should be called after certain state.writing or state.sending changes
+void ICAPModXact::checkConsuming()
{
- // stopSending NULLifies adapted but we do not NULLify virgin.
- // This is assymetric because we want to keep virgin->data even
- // though we are not expecting any more virgin->data->body.
- // TODO: can we cache just the needed headers info instead?
-
- // If they closed first, there is not point (or means) to notify them.
-
- if (state.doneReceiving)
+ // quit if we already stopped or are still using the pipe
+ if (!virgin.body_pipe || !state.doneConsumingVirgin())
return;
- // There is no sendSinkFinished() to notify the other side.
- debugs(93, 7, "ICAPModXact will not receive " << status());
-
- state.doneReceiving = true;
+ debugs(93, 7, HERE << "will stop consuming" << status());
+ stopConsumingFrom(virgin.body_pipe);
}
void ICAPModXact::parseMore()
// note that allocation for echoing is done in handle204NoContent()
void ICAPModXact::maybeAllocateHttpMsg()
{
- if (adapted->data->header) // already allocated
+ if (adapted.header) // already allocated
return;
if (gotEncapsulated("res-hdr")) {
- adapted->data->setHeader(new HttpReply);
+ adapted.setHeader(new HttpReply);
} else if (gotEncapsulated("req-hdr")) {
- adapted->data->setHeader(new HttpRequest);
+ adapted.setHeader(new HttpRequest);
} else
throw TexcHere("Neither res-hdr nor req-hdr in maybeAllocateHttpMsg()");
}
return;
}
- adapted->sendSourceStart();
+ AsyncCall(93,5, initiator, ICAPInitiator::noteIcapHeadersAdapted);
+ cbdataReferenceDone(initiator);
if (state.sending == State::sendingVirgin)
echoMore();
void ICAPModXact::handle100Continue()
{
Must(state.writing == State::writingPaused);
+ // server must not respond before the end of preview: we may send ieof
Must(preview.enabled() && preview.done() && !preview.ieof());
- Must(virginSendClaim.active());
- if (virginSendClaim.limited()) // preview only
+ // 100 "Continue" cancels our preview commitment, not 204s outside preview
+ if (!state.allowedPostview204)
stopBackup();
state.parsing = State::psIcapHeader; // eventually
state.parsing = State::psHttpHeader;
state.sending = State::sendingAdapted;
stopBackup();
+ checkConsuming();
}
void ICAPModXact::handle204NoContent()
{
stopParsing();
- Must(virginSendClaim.active());
- virginSendClaim.protectAll(); // extends protection if needed
- state.sending = State::sendingVirgin;
// We want to clone the HTTP message, but we do not want
- // to copy non-HTTP state parts that HttpMsg kids carry in them.
+ // to copy some non-HTTP state parts that HttpMsg kids carry in them.
// Thus, we cannot use a smart pointer, copy constructor, or equivalent.
// Instead, we simply write the HTTP message and "clone" it by parsing.
- HttpMsg *oldHead = virgin->data->header;
+ HttpMsg *oldHead = virgin.header;
debugs(93, 7, "ICAPModXact cloning virgin message " << oldHead);
MemBuf httpBuf;
packHead(httpBuf, oldHead);
// allocate the adapted message and copy metainfo
- Must(!adapted->data->header);
+ Must(!adapted.header);
HttpMsg *newHead = NULL;
if (const HttpRequest *oldR = dynamic_cast<const HttpRequest*>(oldHead)) {
HttpRequest *newR = new HttpRequest;
- newR->client_addr = oldR->client_addr;
+ inheritVirginProperties(*newR, *oldR);
newHead = newR;
} else
if (dynamic_cast<const HttpReply*>(oldHead))
newHead = new HttpReply;
Must(newHead);
- adapted->data->setHeader(newHead);
+ adapted.setHeader(newHead);
// parse the buffer back
http_status error = HTTP_STATUS_NONE;
httpBuf.clean();
- debugs(93, 7, "ICAPModXact cloned virgin message " << oldHead << " to " << newHead);
+ debugs(93, 7, "ICAPModXact cloned virgin message " << oldHead << " to " <<
+ newHead);
+
+ // setup adapted body pipe if needed
+ if (oldHead->body_pipe != NULL) {
+ debugs(93, 7, HERE << "will echo virgin body from " <<
+ oldHead->body_pipe);
+ state.sending = State::sendingVirgin;
+ checkConsuming();
+ Must(virginBodySending.active());
+ // TODO: optimize: is it possible to just use the oldHead pipe and
+ // remove ICAP from the loop? This echoing is probably a common case!
+ makeAdaptedBodyPipe("echoed virgin response");
+ if (oldHead->body_pipe->bodySizeKnown())
+ adapted.body_pipe->setBodySize(oldHead->body_pipe->bodySize());
+ debugs(93, 7, HERE << "will echo virgin body to " <<
+ adapted.body_pipe);
+ } else {
+ debugs(93, 7, HERE << "no virgin body to echo");
+ stopSending(true);
+ }
}
void ICAPModXact::handleUnknownScode()
if (gotEncapsulated("res-hdr") || gotEncapsulated("req-hdr")) {
maybeAllocateHttpMsg();
- if (!parseHead(adapted->data->header))
+ if (!parseHead(adapted.header))
return; // need more header data
+
+ if (HttpRequest *newHead = dynamic_cast<HttpRequest*>(adapted.header)) {
+ const HttpRequest *oldR = dynamic_cast<const HttpRequest*>(virgin.header);
+ Must(oldR);
+ // TODO: the adapted request did not really originate from the
+ // client; give proxy admin an option to prevent copying of
+ // sensitive client information here. See the following thread:
+ // http://www.squid-cache.org/mail-archive/squid-dev/200703/0040.html
+ inheritVirginProperties(*newHead, *oldR);
+ }
}
- state.parsing = State::psBody;
+ decideOnParsingBody();
}
// parses both HTTP and ICAP headers
return true;
}
-void ICAPModXact::parseBody()
-{
- Must(state.parsing == State::psBody);
+// TODO: Move this method to HttpRequest?
+void ICAPModXact::inheritVirginProperties(HttpRequest &newR, const HttpRequest &oldR) {
- debugs(93, 5, HERE << "have " << readBuf.contentSize() << " body bytes to parse");
+ newR.client_addr = oldR.client_addr;
+ newR.client_port = oldR.client_port;
+
+ newR.my_addr = oldR.my_addr;
+ newR.my_port = oldR.my_port;
+
+ // This may be too conservative for the 204 No Content case
+ // may eventually need cloneNullAdaptationImmune() for that.
+ newR.flags = oldR.flags.cloneAdaptationImmune();
+ if (oldR.auth_user_request) {
+ newR.auth_user_request = oldR.auth_user_request;
+ newR.auth_user_request->lock();
+ }
+}
+
+void ICAPModXact::decideOnParsingBody() {
if (gotEncapsulated("res-body") || gotEncapsulated("req-body")) {
- if (!parsePresentBody()) // need more body data
- return;
+ debugs(93, 5, HERE << "expecting a body");
+ state.parsing = State::psBody;
+ bodyParser = new ChunkedCodingParser;
+ makeAdaptedBodyPipe("adapted response from the ICAP server");
+ Must(state.sending == State::sendingAdapted);
} else {
debugs(93, 5, HERE << "not expecting a body");
+ stopParsing();
+ stopSending(true);
}
-
- stopParsing();
- stopSending(true);
}
-// returns true iff complete body was parsed
-bool ICAPModXact::parsePresentBody()
+void ICAPModXact::parseBody()
{
- if (!bodyParser)
- bodyParser = new ChunkedCodingParser;
+ Must(state.parsing == State::psBody);
+ Must(bodyParser);
- // the parser will throw on errors
- const bool parsed = bodyParser->parse(&readBuf, adapted->data->body);
+ debugs(93, 5, HERE << "have " << readBuf.contentSize() << " body bytes to parse");
- adapted->sendSourceProgress(); // TODO: do not send if parsed nothing
+ // the parser will throw on errors
+ BodyPipeCheckout bpc(*adapted.body_pipe);
+ const bool parsed = bodyParser->parse(&readBuf, &bpc.buf);
+ bpc.checkIn();
debugs(93, 5, HERE << "have " << readBuf.contentSize() << " body bytes after " <<
"parse; parsed all: " << parsed);
- if (parsed)
- return true;
+ if (parsed) {
+ stopParsing();
+ stopSending(true); // the parser succeeds only if all parsed data fits
+ return;
+ }
debugs(93,3,HERE << this << " needsMoreData = " << bodyParser->needsMoreData());
if (bodyParser->needsMoreSpace()) {
Must(!doneSending()); // can hope for more space
- Must(adapted->data->body->hasContent()); // paranoid
- // TODO: there should be a timeout in case the sink is broken.
+ Must(adapted.body_pipe->buf().contentSize() > 0); // paranoid
+ // TODO: there should be a timeout in case the sink is broken
+ // or cannot consume partial content (while we need more space)
}
-
- return false;
}
void ICAPModXact::stopParsing()
if (state.parsing == State::psDone)
return;
- debugs(93, 7, "ICAPModXact will no longer parse " << status());
+ debugs(93, 7, "ICAPModXact will no longer parse" << status());
delete bodyParser;
}
// HTTP side added virgin body data
-void ICAPModXact::noteSourceProgress(MsgPipe *p)
+void ICAPModXact::noteMoreBodyDataAvailable(BodyPipe &)
{
- ICAPXaction_Enter(noteSourceProgress);
+ ICAPXaction_Enter(noteMoreBodyDataAvailable);
- Must(!state.doneReceiving);
writeMore();
if (state.sending == State::sendingVirgin)
}
// HTTP side sent us all virgin info
-void ICAPModXact::noteSourceFinish(MsgPipe *p)
+void ICAPModXact::noteBodyProductionEnded(BodyPipe &)
{
- ICAPXaction_Enter(noteSourceFinish);
+ ICAPXaction_Enter(noteBodyProductionEnded);
- Must(!state.doneReceiving);
- stopReceiving();
+ Must(virgin.body_pipe->productionEnded());
// push writer and sender in case we were waiting for the last-chunk
writeMore();
ICAPXaction_Exit();
}
-// HTTP side is aborting
-void ICAPModXact::noteSourceAbort(MsgPipe *p)
+// body producer aborted
+void ICAPModXact::noteBodyProducerAborted(BodyPipe &)
{
- ICAPXaction_Enter(noteSourceAbort);
+ ICAPXaction_Enter(noteBodyProducerAborted);
+
+ mustStop("virgin HTTP body producer aborted");
- Must(!state.doneReceiving);
- stopReceiving();
- mustStop("HTTP source quit");
+ ICAPXaction_Exit();
+}
+
+// initiator aborted
+void ICAPModXact::noteInitiatorAborted()
+{
+ ICAPXaction_Enter(noteInitiatorAborted);
+
+ if (initiator) {
+ cbdataReferenceDone(initiator);
+ mustStop("initiator aborted");
+ }
ICAPXaction_Exit();
}
-// HTTP side wants more adapted data and possibly freed some buffer space
-void ICAPModXact::noteSinkNeed(MsgPipe *p)
+// adapted body consumer wants more adapted data and
+// possibly freed some buffer space
+void ICAPModXact::noteMoreBodySpaceAvailable(BodyPipe &)
{
- ICAPXaction_Enter(noteSinkNeed);
+ ICAPXaction_Enter(noteMoreBodySpaceAvailable);
if (state.sending == State::sendingVirgin)
echoMore();
ICAPXaction_Exit();
}
-// HTTP side aborted
-void ICAPModXact::noteSinkAbort(MsgPipe *p)
+// adapted body consumer aborted
+void ICAPModXact::noteBodyConsumerAborted(BodyPipe &)
{
- ICAPXaction_Enter(noteSinkAbort);
+ ICAPXaction_Enter(noteBodyConsumerAborted);
- mustStop("HTTP sink quit");
+ mustStop("adapted body consumer aborted");
ICAPXaction_Exit();
}
// internal cleanup
-void ICAPModXact::doStop()
+void ICAPModXact::swanSong()
{
- debugs(93, 5, HERE << "doStop() called");
- ICAPXaction::doStop();
+ debugs(93, 5, HERE << "swan sings" << status());
+
+ if (initiator) {
+debugs(93, 2, HERE << "swan sings for " << stopReason << status());
+ AsyncCall(93,5, initiator, ICAPInitiator::noteIcapHeadersAborted);
+ cbdataReferenceDone(initiator);
+ }
stopWriting(false);
stopBackup();
stopSending(false);
- // see stopReceiving() for reasons it cannot NULLify virgin there
-
- if (virgin != NULL) {
- if (!state.doneReceiving)
- virgin->sendSinkAbort();
- else
- virgin->sink = NULL;
-
- virgin = NULL; // refcounted
- }
-
- if (self != NULL) {
- Pointer s = self;
- self = NULL;
- ICAPNoteXactionDone(s);
- /* this object may be destroyed when 's' is cleared */
- }
+ ICAPXaction::swanSong();
}
void ICAPModXact::makeRequestHeaders(MemBuf &buf)
// build HTTP request header, if any
ICAP::Method m = s.method;
- const HttpRequest *request = virgin->data->cause ?
- virgin->data->cause :
- dynamic_cast<const HttpRequest*>(virgin->data->header);
+ const HttpRequest *request = virgin.cause ?
+ virgin.cause :
+ dynamic_cast<const HttpRequest*>(virgin.header);
- // to simplify, we could we assume that request is always available
+ // to simplify, we could assume that request is always available
String urlPath;
if (request) {
encapsulateHead(buf, "req-hdr", httpBuf, request);
else
if (ICAP::methodReqmod == m)
- encapsulateHead(buf, "req-hdr", httpBuf, virgin->data->header);
+ encapsulateHead(buf, "req-hdr", httpBuf, virgin.header);
}
if (ICAP::methodRespmod == m)
- if (const MsgPipeData::Header *prime = virgin->data->header)
+ if (const HttpMsg *prime = virgin.header)
encapsulateHead(buf, "res-hdr", httpBuf, prime);
if (!virginBody.expected())
if (shouldPreview(urlPath)) {
buf.Printf("Preview: %d\r\n", (int)preview.ad());
- virginSendClaim.protectUpTo(preview.ad());
+ if (virginBody.expected()) // there is a body to preview
+ virginBodySending.plan();
+ else
+ finishNullOrEmptyBodyPreview(httpBuf);
}
if (shouldAllow204()) {
+ debugs(93,5, HERE << "will allow 204s outside of preview");
+ state.allowedPostview204 = true;
buf.Printf("Allow: 204\r\n");
- // be robust: do not rely on the expected body size
- virginSendClaim.protectAll();
+ if (virginBody.expected()) // there is a body to echo
+ virginBodySending.plan();
}
if (TheICAPConfig.send_client_ip && request)
buf.Printf("X-Client-IP: %s\r\n", inet_ntoa(request->client_addr));
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());
+ makeUsernameHeader(request, buf);
// fprintf(stderr, "%s\n", buf.content());
httpBuf.clean();
}
+void ICAPModXact::makeUsernameHeader(const HttpRequest *request, MemBuf &buf) {
+ if (const auth_user_request_t *auth = request->auth_user_request) {
+ if (char const *name = auth->username()) {
+ const char *value = TheICAPConfig.client_username_encode ?
+ base64_encode(name) : name;
+ buf.Printf("%s: %s\r\n", TheICAPConfig.client_username_header,
+ value);
+ }
+ }
+}
+
void ICAPModXact::encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf &httpBuf, const HttpMsg *head)
{
// update ICAP header
// decides whether to offer a preview and calculates its size
bool ICAPModXact::shouldPreview(const String &urlPath)
{
- size_t wantedSize;
-
if (!TheICAPConfig.preview_enable) {
debugs(93, 5, HERE << "preview disabled by squid.conf");
return false;
}
+ size_t wantedSize;
+
if (!service().wantsPreview(urlPath, wantedSize)) {
debugs(93, 5, "ICAPModXact should not offer preview for " << urlPath);
return false;
// cannot preview more than we can backup
size_t ad = XMIN(wantedSize, TheBackupLimit);
- if (virginBody.expected() && virginBody.knownSize())
- ad = XMIN(ad, virginBody.size()); // not more than we have
+ if (!virginBody.expected())
+ ad = 0;
else
- ad = 0; // questionable optimization?
+ if (virginBody.knownSize())
+ ad = XMIN(ad, virginBody.size()); // not more than we have
debugs(93, 5, "ICAPModXact should offer " << ad << "-byte preview " <<
"(service wanted " << wantedSize << ")");
preview.enable(ad);
+ Must(preview.enabled());
- return preview.enabled();
+ return true;
}
// decides whether to allow 204 responses
return virginBody.size() < TheBackupLimit;
}
+// Normally, the body-writing code handles preview body. It can deal with
+// bodies of unexpected size, including those that turn out to be empty.
+// However, that code assumes that the body was expected and body control
+// structures were initialized. This is not the case when there is no body
+// or the body is known to be empty, because the virgin message will lack a
+// body_pipe. So we handle preview of null-body and zero-size bodies here.
+void ICAPModXact::finishNullOrEmptyBodyPreview(MemBuf &buf)
+{
+ Must(!virginBodyWriting.active()); // one reason we handle it here
+ Must(!virgin.body_pipe); // another reason we handle it here
+ Must(!preview.ad());
+
+ // do not add last-chunk because our Encapsulated header says null-body
+ // addLastRequestChunk(buf);
+ preview.wrote(0, true);
+
+ Must(preview.done());
+ Must(preview.ieof());
+}
+
void ICAPModXact::fillPendingStatus(MemBuf &buf) const
{
ICAPXaction::fillPendingStatus(buf);
if (state.serviceWaiting)
buf.append("U", 1);
- if (!state.doneReceiving)
+ if (virgin.body_pipe != NULL)
buf.append("R", 1);
- if (!doneReading())
+ if (connection > 0 && !doneReading())
buf.append("r", 1);
if (!state.doneWriting() && state.writing != State::writingInit)
buf.Printf("P(%d)", (int) preview.debt());
}
- if (virginSendClaim.active())
+ if (virginBodySending.active())
buf.append("B", 1);
if (!state.doneParsing() && state.parsing != State::psIcapHeader)
{
ICAPXaction::fillDoneStatus(buf);
- if (state.doneReceiving)
+ if (!virgin.body_pipe)
buf.append("R", 1);
if (state.doneWriting())
// calculate whether there is a virgin HTTP body and
// whether its expected size is known
+// TODO: rename because we do not just estimate
void ICAPModXact::estimateVirginBody()
{
- // note: defaults should be fine but will disable previews and 204s
+ // note: lack of size info may disable previews and 204s
- Must(virgin != NULL && virgin->data->header);
+ HttpMsg *msg = virgin.header;
+ Must(msg);
method_t method;
- if (virgin->data->cause)
- method = virgin->data->cause->method;
+ if (virgin.cause)
+ method = virgin.cause->method;
else
- if (HttpRequest *req = dynamic_cast<HttpRequest*>(virgin->data->
- header))
- method = req->method;
- else
- return;
+ if (HttpRequest *req = dynamic_cast<HttpRequest*>(msg))
+ method = req->method;
+ else
+ method = METHOD_NONE;
ssize_t size;
- if (virgin->data->header->expectingBody(method, size)) {
- virginBody.expect(size)
- ;
- debugs(93, 6, "ICAPModXact expects virgin body; size: " << size);
+ // expectingBody returns true for zero-sized bodies, but we will not
+ // get a pipe for that body, so we treat the message as bodyless
+ if (method != METHOD_NONE && msg->expectingBody(method, size) && size) {
+ debugs(93, 6, "ICAPModXact expects virgin body from " <<
+ virgin.body_pipe << "; size: " << size);
+
+ virginBody.expect(size);
+ virginBodyWriting.plan();
+
+ // sign up as a body consumer
+ Must(msg->body_pipe != NULL);
+ Must(msg->body_pipe == virgin.body_pipe);
+ Must(virgin.body_pipe->setConsumerIfNotLate(this));
+
+ // make sure TheBackupLimit is in-sync with the buffer size
+ Must(TheBackupLimit <= static_cast<size_t>(msg->body_pipe->buf().max_capacity));
} else {
debugs(93, 6, "ICAPModXact does not expect virgin body");
+ Must(msg->body_pipe == NULL);
+ checkConsuming();
}
}
+void ICAPModXact::makeAdaptedBodyPipe(const char *what) {
+ Must(!adapted.body_pipe);
+ Must(!adapted.header->body_pipe);
+ adapted.header->body_pipe = new BodyPipe(this);
+ adapted.body_pipe = adapted.header->body_pipe;
+ debugs(93, 7, HERE << "will supply " << what << " via " <<
+ adapted.body_pipe << " pipe");
+}
+
// TODO: Move SizedEstimate, MemBufBackup, and ICAPPreview elsewhere
-MemBufClaim::MemBufClaim(): theStart(-1), theGoal(-1)
+VirginBodyAct::VirginBodyAct(): theStart(-1)
{}
-void MemBufClaim::protectAll()
-{
- if (theStart < 0)
- theStart = 0;
-
- theGoal = -1; // no specific goal
-}
-
-void MemBufClaim::protectUpTo(size_t aGoal)
+void VirginBodyAct::plan()
{
if (theStart < 0)
theStart = 0;
-
- Must(aGoal >= 0);
-
- theGoal = (theGoal < 0) ? static_cast<ssize_t>(aGoal) :
- XMIN(static_cast<ssize_t>(aGoal), theGoal);
}
-void MemBufClaim::disable()
+void VirginBodyAct::disable()
{
- theStart = -1;
+ theStart = -2;
}
-void MemBufClaim::release(size_t size)
+void VirginBodyAct::progress(size_t size)
{
Must(active());
Must(size >= 0);
theStart += static_cast<ssize_t>(size);
-
- if (limited() && theStart >= theGoal)
- disable();
}
-size_t MemBufClaim::offset() const
+size_t VirginBodyAct::offset() const
{
Must(active());
return static_cast<size_t>(theStart);
}
-bool MemBufClaim::limited() const
-{
- Must(active());
- return theGoal >= 0;
-}
-
ICAPPreview::ICAPPreview(): theWritten(0), theAd(0), theState(stDisabled)
{}
void ICAPPreview::wrote(size_t size, bool wroteEof)
{
Must(enabled());
+
theWritten += size;
+ Must(theWritten <= theAd);
+
+ if (wroteEof)
+ theState = stIeof; // written size is irrelevant
+ else
if (theWritten >= theAd)
- theState = stDone; // wroteEof is irrelevant
- else
- if (wroteEof)
- theState = stIeof;
+ theState = stDone;
}
bool ICAPModXact::fillVirginHttpHeader(MemBuf &mb) const
{
- if (virgin == NULL)
- return false;
-
- if (virgin->data == NULL)
- return false;
-
- if (virgin->data->header == NULL)
+ if (virgin.header == NULL)
return false;
- virgin->data->header->firstLineBuf(mb);
+ virgin.header->firstLineBuf(mb);
return true;
}
/*
- * $Id: ICAPModXact.h,v 1.6 2006/10/31 23:30:58 wessels Exp $
+ * $Id: ICAPModXact.h,v 1.7 2007/04/06 04:50:07 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
#define SQUID_ICAPMODXACT_H
#include "ICAPXaction.h"
-#include "MsgPipe.h"
-#include "MsgPipeSource.h"
-#include "MsgPipeSink.h"
+#include "ICAPInOut.h"
+#include "BodyPipe.h"
+
+/*
+ * ICAPModXact implements ICAP REQMOD and RESPMOD transaction using
+ * ICAPXaction as the base. The ICAPModXact receives a virgin HTTP message
+ * from an ICAP vecoring point, (a.k.a., initiator), communicates with the
+ * ICAP server, and sends the adapted HTTP message headers back.
+ * Virgin/adapted HTTP message body is reveived/sent using BodyPipe
+ * interface. The initiator (or its associate) is expected to send and/or
+ * receive the HTTP body.
+ */
-/* ICAPModXact implements ICAP REQMOD and RESPMOD transaction using ICAPXaction
- * as the base. It implements message pipe sink and source interfaces for
- * communication with various HTTP "anchors" and "hooks". ICAPModXact receives
- * virgin HTTP messages, communicates with the ICAP server, and sends the
- * adapted messages back. ICAPClient is the "owner" of the ICAPModXact. */
class ChunkedCodingParser;
ssize_t theData; // combines expectation and size info to save RAM
};
-// Protects buffer area. If area size is unknown, protects buffer suffix.
-// Only "released" data can be consumed by the caller. Used to maintain
-// write, preview, and 204 promises for ICAPModXact virgin->data-body buffer.
-
-class MemBufClaim
+// Virgin body may be used for two activities: (a) writing preview or prime
+// body to the ICAP server and (b) sending the body back in the echo mode.
+// Both activities use the same BodyPipe and may be active at the same time.
+// This class is used to maintain the state of body writing or sending
+// activity and to coordinate consumption of the shared virgin body buffer.
+class VirginBodyAct
{
public:
- MemBufClaim();
+ VirginBodyAct(); // disabled by default
- void protectAll();
- void protectUpTo(size_t aGoal);
- void disable();
- bool active() const { return theStart >= 0; }
+ void plan(); // the activity may happen; do not consume at or above offset
+ void disable(); // the activity wont continue; no consumption restrictions
+ bool active() const { return theStart >= 0; } // planned and not disabled
// methods below require active()
- void release(size_t size); // stop protecting size more bytes
- size_t offset() const; // protected area start
- bool limited() const; // protects up to a known size goal
+ size_t offset() const; // the absolute beginning of not-yet-acted-on data
+ void progress(size_t size); // note processed body bytes
private:
- ssize_t theStart; // left area border
- ssize_t theGoal; // "end" maximum, if any
+ ssize_t theStart; // offset, unless negative.
};
+
// maintains preview-related sizes
class ICAPPreview
enum State { stDisabled, stWriting, stIeof, stDone } theState;
};
-class ICAPModXact: public ICAPXaction, public MsgPipeSource, public MsgPipeSink
+class ICAPInitiator;
+
+class ICAPModXact: public ICAPXaction, public BodyProducer, public BodyConsumer
{
public:
typedef RefCount<ICAPModXact> Pointer;
public:
- ICAPModXact();
+ ICAPModXact(ICAPInitiator *anInitiator, HttpMsg *virginHeader, HttpRequest *virginCause, ICAPServiceRep::Pointer &s);
- // called by ICAPClient
- void init(ICAPServiceRep::Pointer&, MsgPipe::Pointer &aVirgin, MsgPipe::Pointer &anAdapted, Pointer &aSelf);
+ // communication with the initiator
+ void noteInitiatorAborted();
+ AsyncCallWrapper(93,3, ICAPModXact, noteInitiatorAborted)
- // pipe source methods; called by Anchor while receiving the adapted msg
- virtual void noteSinkNeed(MsgPipe *p);
- virtual void noteSinkAbort(MsgPipe *p);
+ // BodyProducer methods
+ virtual void noteMoreBodySpaceAvailable(BodyPipe &);
+ virtual void noteBodyConsumerAborted(BodyPipe &);
- // pipe sink methods; called by ICAP while sending the virgin message
- virtual void noteSourceStart(MsgPipe *p);
- virtual void noteSourceProgress(MsgPipe *p);
- virtual void noteSourceFinish(MsgPipe *p);
- virtual void noteSourceAbort(MsgPipe *p);
+ // BodyConsumer methods
+ virtual void noteMoreBodyDataAvailable(BodyPipe &);
+ virtual void noteBodyProductionEnded(BodyPipe &);
+ virtual void noteBodyProducerAborted(BodyPipe &);
// comm handlers
virtual void handleCommConnected();
// service waiting
void noteServiceReady();
+public:
+ ICAPInOut virgin;
+ ICAPInOut adapted;
+
private:
+ virtual void start();
+
void estimateVirginBody();
+ void makeAdaptedBodyPipe(const char *what);
void waitForService();
void startWriting();
void writeMore();
- void writePriviewBody();
+ void writePreviewBody();
void writePrimeBody();
void writeSomeBody(const char *label, size_t size);
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;
+ size_t virginContentSize(const VirginBodyAct &act) const;
+ const char *virginContentData(const VirginBodyAct &act) const;
+ bool virginBodyEndReached(const VirginBodyAct &act) const;
+
void makeRequestHeaders(MemBuf &buf);
- void moveRequestChunk(MemBuf &buf, size_t chunkSize);
+ void makeUsernameHeader(const HttpRequest *request, MemBuf &buf);
void addLastRequestChunk(MemBuf &buf);
void openChunk(MemBuf &buf, size_t chunkSize, bool ieof);
void closeChunk(MemBuf &buf);
void virginConsume();
+ void finishNullOrEmptyBodyPreview(MemBuf &buf);
bool shouldPreview(const String &urlPath);
bool shouldAllow204();
void parseIcapHead();
void parseHttpHead();
bool parseHead(HttpMsg *head);
+ void inheritVirginProperties(HttpRequest &newR, const HttpRequest &oldR);
+ void decideOnParsingBody();
void parseBody();
- bool parsePresentBody();
void maybeAllocateHttpMsg();
void handle100Continue();
void echoMore();
virtual bool doneAll() const;
+ virtual void swanSong();
- virtual void doStop();
void stopReceiving();
void stopSending(bool nicely);
void stopWriting(bool nicely);
void packHead(MemBuf &httpBuf, const HttpMsg *head);
void encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf &httpBuf, const HttpMsg *head);
bool gotEncapsulated(const char *section) const;
+ void checkConsuming();
- Pointer self;
- MsgPipe::Pointer virgin;
- MsgPipe::Pointer adapted;
+ ICAPInitiator *initiator;
HttpReply *icapReply;
SizedEstimate virginBody;
- MemBufClaim virginWriteClaim; // preserve virgin data buffer for writing
- MemBufClaim virginSendClaim; // ... for sending (previe and 204s)
- size_t virginConsumed; // virgin data consumed so far
+ VirginBodyAct virginBodyWriting; // virgin body writing state
+ VirginBodyAct virginBodySending; // virgin body sending state
+ size_t virginConsumed; // virgin data consumed so far
ICAPPreview preview; // use for creating (writing) the preview
ChunkedCodingParser *bodyParser; // ICAP response body parser
public:
- unsigned serviceWaiting:
- 1; // waiting for the ICAPServiceRep preparing the ICAP service
-
- unsigned doneReceiving:
- 1; // expect no new virgin info (from the virgin pipe)
+ bool serviceWaiting; // waiting for ICAP service options
+ bool allowedPostview204; // mmust handle 204 No Content outside preview
// will not write anything [else] to the ICAP server connection
bool doneWriting() const { return writing == writingReallyDone; }
+ // will not use virgin.body_pipe
+ bool doneConsumingVirgin() const { return writing >= writingAlmostDone
+ && (sending == sendingAdapted || sending == sendingDone); }
+
// parsed entire ICAP response from the ICAP server
bool doneParsing() const { return parsing == psDone; }
CBDATA_CLASS_INIT(ICAPOptXact);
-ICAPOptXact::ICAPOptXact(): ICAPXaction("ICAPOptXact"), options(NULL),
- cb(NULL), cbData(NULL)
-
+ICAPOptXact::ICAPOptXact(ICAPServiceRep::Pointer &aService, Callback *aCbAddr, void *aCbData):
+ ICAPXaction("ICAPOptXact"),
+ cbAddr(aCbAddr), cbData(cbdataReference(aCbData))
{
+ Must(aCbAddr && aCbData);
+ service(aService);
}
ICAPOptXact::~ICAPOptXact()
{
- Must(!options); // the caller must set to NULL
+ if (cbAddr) {
+ debugs(93, 1, HERE << "BUG: exiting without sending options");
+ cbdataReferenceDone(cbData);
+ }
}
-void ICAPOptXact::start(ICAPServiceRep::Pointer &aService, Callback *aCb, void *aCbData)
+void ICAPOptXact::start()
{
ICAPXaction_Enter(start);
- service(aService);
- Must(!cb && aCb && aCbData);
- cb = aCb;
- cbData = cbdataReference(aCbData);
+ ICAPXaction::start();
+
+ Must(self != NULL); // set by AsyncStart;
openConnection();
scheduleWrite(requestBuf);
}
-bool ICAPOptXact::doneAll() const
-{
- return options && ICAPXaction::doneAll();
-}
-
-
-void ICAPOptXact::doStop()
-{
- ICAPXaction::doStop();
-
- if (Callback *call = cb) {
- cb = NULL;
- void *data = NULL;
-
- if (cbdataReferenceValidDone(cbData, &data)) {
- (*call)(this, data); // will delete us
- return;
- }
- }
-
- // get rid of options if we did not call the callback
- delete options;
-
- options = NULL;
-}
-
void ICAPOptXact::makeRequest(MemBuf &buf)
{
const ICAPServiceRep &s = service();
// comm module read a portion of the ICAP response for us
void ICAPOptXact::handleCommRead(size_t)
{
- if (parseResponse())
+ if (ICAPOptions *options = parseResponse()) {
+ sendOptions(options);
Must(done()); // there should be nothing else to do
- else
- scheduleRead();
+ return;
+ }
+
+ scheduleRead();
}
-bool ICAPOptXact::parseResponse()
+ICAPOptions *ICAPOptXact::parseResponse()
{
debugs(93, 5, HERE << "have " << readBuf.contentSize() << " bytes to parse" <<
status());
HttpReply *r = new HttpReply;
r->protoPrefix = "ICAP/"; // TODO: make an IcapReply class?
- if (!parseHttpMsg(r)) {
+ if (!parseHttpMsg(r)) { // throws on errors
delete r;
- return false;
+ return 0;
}
- options = new ICAPOptions;
-
- options->configure(r);
-
if (httpHeaderHasConnDir(&r->header, "close"))
reuseConnection = false;
+ ICAPOptions *options = new ICAPOptions;
+ options->configure(r);
+
delete r;
- return true;
+ return options;
+}
+
+void ICAPOptXact::swanSong() {
+ if (cbAddr) {
+ debugs(93, 3, HERE << "probably failed; sending NULL options");
+ sendOptions(0);
+ }
+ ICAPXaction::swanSong();
+}
+
+void ICAPOptXact::sendOptions(ICAPOptions *options) {
+ debugs(93, 3, HERE << "sending options " << options << " to " << cbData <<
+ " at " << (void*)cbAddr << status());
+
+ Must(cbAddr);
+ Callback *addr = cbAddr;
+ cbAddr = NULL; // in case the callback calls us or throws
+
+ void *data = NULL;
+ if (cbdataReferenceValidDone(cbData, &data))
+ (*addr)(options, data); // callee takes ownership of the options
+ else
+ debugs(93, 2, HERE << "sending options " << options << " to " <<
+ data << " failed" << status());
}
/*
- * $Id: ICAPOptXact.h,v 1.4 2006/10/31 23:30:58 wessels Exp $
+ * $Id: ICAPOptXact.h,v 1.5 2007/04/06 04:50:07 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
{
public:
- typedef void Callback(ICAPOptXact*, void *data);
+ typedef void Callback(ICAPOptions *newOptions, void *callerData);
- ICAPOptXact();
+ ICAPOptXact(ICAPServiceRep::Pointer &aService, Callback *aCb, void *aCbData);
virtual ~ICAPOptXact();
- void start(ICAPServiceRep::Pointer &aService, Callback *aCb, void *aCbData);
-
- ICAPOptions *options; // result for the caller to take/handle
-
protected:
+ virtual void start();
virtual void handleCommConnected();
virtual void handleCommWrote(size_t size);
virtual void handleCommRead(size_t size);
- virtual bool doneAll() const;
+ virtual void swanSong();
void makeRequest(MemBuf &buf);
- bool parseResponse();
+ ICAPOptions *parseResponse();
+ void sendOptions(ICAPOptions *options);
void startReading();
- virtual void doStop();
-
private:
- Callback *cb;
- void *cbData;
+ Callback *cbAddr; // callback to call with newly fetched options
+ void *cbData; // callback data
CBDATA_CLASS2(ICAPOptXact);
};
+// TODO: replace the callback API with a class-base interface?
+
#endif /* SQUID_ICAPOPTXACT_H */
return squid_curtime <= expire();
}
+int ICAPOptions::ttl() const
+{
+ Must(valid());
+ return theTTL >= 0 ? theTTL : TheICAPConfig.default_options_ttl;
+}
+
time_t ICAPOptions::expire() const
{
Must(valid());
- return theTTL >= 0 ? theTimestamp + theTTL : -1;
+ return theTimestamp + ttl();
}
void ICAPOptions::configure(const HttpReply *reply)
cfgIntHeader(h, "Options-TTL", theTTL);
- if (theTTL < 0)
- theTTL = TheICAPConfig.default_options_ttl;
-
theTimestamp = h->getTime(HDR_DATE);
if (theTimestamp < 0)
/*
- * $Id: ICAPOptions.h,v 1.8 2006/10/31 23:30:58 wessels Exp $
+ * $Id: ICAPOptions.h,v 1.9 2007/04/06 04:50:07 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
#define SQUID_ICAPOPTIONS_H
#include "squid.h"
-#include "ICAPClient.h"
+#include "ICAPServiceRep.h"
class wordlist;
bool valid() const;
bool fresh() const;
+ int ttl() const;
time_t expire() const;
time_t timestamp() const { return theTimestamp; };
- int ttl() const { return theTTL; };
-
typedef enum { xferNone, xferPreview, xferIgnore, xferComplete } TransferKind;
TransferKind transferKind(const String &urlPath) const;
#include "ICAPOptions.h"
#include "ICAPOptXact.h"
#include "ConfigParser.h"
+#include "ICAPConfig.h"
#include "SquidTime.h"
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),
theOptions(NULL), theLastUpdate(0),
void ICAPServiceRep::noteFailure() {
++theSessionFailures;
debugs(93,4, "ICAPService failure " << theSessionFailures <<
- ", out of " << TheSessionFailureLimit << " allowed");
+ ", out of " << TheICAPConfig.service_failure_limit << " allowed");
- if (theSessionFailures > TheSessionFailureLimit)
+ if (TheICAPConfig.service_failure_limit >= 0 &&
+ theSessionFailures > TheICAPConfig.service_failure_limit)
suspend("too many failures");
// TODO: Should bypass setting affect how much Squid tries to talk to
void ICAPServiceRep::callWhenReady(Callback *cb, void *data)
{
+ debugs(93,5, HERE << "ICAPService is asked to call " << data <<
+ " when ready " << status());
+
Must(cb);
Must(self != NULL);
Must(!broken()); // we do not wait for a broken service
void ICAPServiceRep::changeOptions(ICAPOptions *newOptions)
{
- debugs(93,9, "ICAPService changes options from " << theOptions << " to " <<
- newOptions);
+ debugs(93,8, "ICAPService changes options from " << theOptions << " to " <<
+ newOptions << ' ' << status());
delete theOptions;
theOptions = newOptions;
/*
* 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!");
+ const int skew = (int)(theOptions->timestamp() - squid_curtime);
+ if (abs(skew) > theOptions->ttl()) {
+ // TODO: If skew is negative, the option will be considered down
+ // because of stale options. We should probably change this.
+ debugs(93, 1, "ICAP service's clock is skewed by " << skew <<
+ " seconds: " << uri.buf());
+ }
}
void ICAPServiceRep::announceStatusChange(const char *downPhrase, bool important) const
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);
+ debugs(93,level, what << " ICAP service is " << state << ": " << uri <<
+ ' ' << status());
wasAnnouncedUp = !wasAnnouncedUp;
}
static
-void ICAPServiceRep_noteNewOptions(ICAPOptXact *x, void *data)
+void ICAPServiceRep_noteNewOptions(ICAPOptions *newOptions, void *data)
{
ICAPServiceRep *service = static_cast<ICAPServiceRep*>(data);
Must(service);
- service->noteNewOptions(x);
+ service->noteNewOptions(newOptions);
}
-void ICAPServiceRep::noteNewOptions(ICAPOptXact *x)
+void ICAPServiceRep::noteNewOptions(ICAPOptions *newOptions)
{
- Must(x);
+ // newOptions may be NULL
+
Must(waiting);
waiting = false;
- changeOptions(x->options);
- x->options = NULL;
- delete x;
+ changeOptions(newOptions);
debugs(93,3, "ICAPService got new options and is now " << status());
debugs(93,6, "ICAPService will get new options " << status());
waiting = true;
- ICAPOptXact *x = new ICAPOptXact;
- x->start(self, &ICAPServiceRep_noteNewOptions, this);
+ ICAPOptXact::AsyncStart(
+ new ICAPOptXact(self, &ICAPServiceRep_noteNewOptions, this));
// TODO: timeout in case ICAPOptXact never calls us back?
}
const time_t expire = theOptions->expire();
debugs(93,7, "ICAPService options expire on " << expire << " >= " << squid_curtime);
- if (expire < 0) // unknown expiration time
- when = squid_curtime + 60*60;
- else
- if (expire < expectedWait) // invalid expiration time
+ // Unknown or invalid (too small) expiration times should not happen.
+ // ICAPOptions should use the default TTL, and ICAP servers should not
+ // send invalid TTLs, but bugs and attacks happen.
+ if (expire < expectedWait)
when = squid_curtime + 60*60;
else
when = expire - expectedWait; // before the current options expire
} else {
- when = squid_curtime + 3*60; // delay for a down service
+ // delay for a down service
+ when = squid_curtime + TheICAPConfig.service_revival_delay;
}
- debugs(93,7, "ICAPService options raw update on " << when << " or " << (when - squid_curtime));
+ debugs(93,7, "ICAPService options raw update at " << when << " or in " <<
+ (when - squid_curtime) << " sec");
+
+ /* adjust update time to prevent too-frequent updates */
+
if (when < squid_curtime)
when = squid_curtime;
- const int minUpdateGap = 1*60; // seconds
+ const int minUpdateGap = expectedWait + 10; // seconds
if (when < theLastUpdate + minUpdateGap)
when = theLastUpdate + minUpdateGap;
- // TODO: keep the time of the last update to prevet too-frequent updates
-
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);
updateScheduled = true;
if (up())
buf.append("up", 2);
- else
+ else {
buf.append("down", 4);
+ if (!self)
+ buf.append(",gone", 5);
+ if (isSuspended)
+ buf.append(",susp", 5);
- if (!self)
- buf.append(",gone", 5);
+ if (!theOptions)
+ buf.append(",!opt", 5);
+ else
+ if (!theOptions->valid())
+ buf.append(",!valid", 7);
+ else
+ if (!theOptions->fresh())
+ buf.append(",stale", 6);
+ }
if (waiting)
buf.append(",wait", 5);
buf.append(",notif", 6);
if (theSessionFailures > 0)
- buf.Printf(",F%d", theSessionFailures);
-
- if (isSuspended)
- buf.append(",susp", 5);
+ buf.Printf(",fail%d", theSessionFailures);
buf.append("]", 1);
buf.terminate();
/*
- * $Id: ICAPServiceRep.h,v 1.5 2006/10/31 23:30:58 wessels Exp $
+ * $Id: ICAPServiceRep.h,v 1.6 2007/04/06 04:50:08 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
public: // treat these as private, they are for callbacks only
void noteTimeToUpdate();
void noteTimeToNotify();
- void noteNewOptions(ICAPOptXact *x);
+ void noteNewOptions(ICAPOptions *newOptions);
private:
// stores Prepare() callback info
#include "squid.h"
#include "comm.h"
-#include "HttpReply.h"
+#include "HttpMsg.h"
#include "ICAPXaction.h"
-#include "ICAPClient.h"
#include "TextException.h"
#include "pconn.h"
#include "fde.h"
static PconnPool *icapPconnPool = new PconnPool("ICAP Servers");
+int ICAPXaction::TheLastId = 0;
+
+//CBDATA_CLASS_INIT(ICAPXaction);
+
/* comm module handlers (wrappers around corresponding ICAPXaction methods */
// TODO: Teach comm module to call object methods directly
-//CBDATA_CLASS_INIT(ICAPXaction);
-
static
ICAPXaction &ICAPXaction_fromData(void *data)
{
ICAPXaction_fromData(data).noteCommRead(status, size);
}
+ICAPXaction *ICAPXaction::AsyncStart(ICAPXaction *x) {
+ assert(x != NULL);
+ x->self = x; // yes, this works with the current RefCount cimplementation
+ AsyncCall(93,5, x, ICAPXaction::start);
+ return x;
+}
+
ICAPXaction::ICAPXaction(const char *aTypeName):
+ id(++TheLastId),
connection(-1),
commBuf(NULL), commBufSize(0),
commEof(false),
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
- Must(static_cast<size_t>(readBuf.potentialSpaceSize()) <= commBufSize);
+ debugs(93,3, typeName << " constructed, this=" << this <<
+ " [icapx" << id << ']'); // we should not call virtual status() here
}
ICAPXaction::~ICAPXaction()
{
- debug(93,3)("%s destructing, this=%p\n", typeName, this);
- doStop();
- readBuf.clean();
- memFreeBuf(commBufSize, commBuf);
+ debugs(93,3, typeName << " destructed, this=" << this <<
+ " [icapx" << id << ']'); // we should not call virtual status() here
+}
+
+void ICAPXaction::start()
+{
+ debugs(93,3, HERE << typeName << " starts" << status());
+
+ Must(self != NULL); // set by AsyncStart;
+
+ 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
+ Must(static_cast<size_t>(readBuf.potentialSpaceSize()) <= commBufSize);
}
// TODO: obey service-specific, OPTIONS-reported connection limit
if (connection >= 0) {
debugs(93,3, HERE << "reused pconn FD " << connection);
+ connector = &ICAPXaction_noteCommConnected; // make doneAll() false
eventAdd("ICAPXaction::reusedConnection",
reusedConnection,
this,
{
debug(93,5)("ICAPXaction::reusedConnection\n");
ICAPXaction *x = (ICAPXaction*)data;
- /*
- * XXX noteCommConnected Must()s that connector is set to something;
- */
- x->connector = &ICAPXaction_noteCommConnected;
x->noteCommConnected(COMM_OK);
}
cancelRead(); // may not work
- if (reuseConnection && (writer || reader)) {
- debugs(93,5, HERE << "not reusing pconn due to pending I/O " << status());
+ if (reuseConnection && !doneWithIo()) {
+ debugs(93,5, HERE << "not reusing pconn due to pending I/O" << status());
reuseConnection = false;
}
if (reuseConnection) {
- debugs(93,3, HERE << "pushing pconn " << status());
+ 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 " << status());
+ debugs(93,3, HERE << "closing pconn" << status());
// comm_close will clear timeout
comm_close(connection);
}
void ICAPXaction::mustStop(const char *aReason)
{
- Must(inCall); // otherwise nobody will call doStop()
+ Must(inCall); // otherwise nobody will delete us if we are done()
Must(aReason);
if (!stopReason) {
stopReason = aReason;
}
}
-// internal cleanup
-void ICAPXaction::doStop()
+// This 'last chance' method is called before a 'done' transaction is deleted.
+// It is wrong to call virtual methods from a destructor. Besides, this call
+// indicates that the transaction will terminate as planned.
+void ICAPXaction::swanSong()
{
- debugs(93, 5, typeName << "::doStop " << status());
+ // kids should sing first and then call the parent method.
+
+ closeConnection(); // TODO: rename because we do not always close
+
+ if (!readBuf.isNull())
+ readBuf.clean();
+
+ if (commBuf)
+ memFreeBuf(commBufSize, commBuf);
- closeConnection(); // TODO: pconn support: close iff bad connection
+ debugs(93, 5, HERE << "swan sang" << status());
}
void ICAPXaction::service(ICAPServiceRep::Pointer &aService)
bool ICAPXaction::callStart(const char *method)
{
- debugs(93, 5, typeName << "::" << method << " called " << status());
+ debugs(93, 5, typeName << "::" << method << " called" << status());
if (inCall) {
// this may happen when we have bugs or when arguably buggy
// comm interface calls us while we are closing the connection
- debugs(93, 5, typeName << "::" << inCall << " is in progress; " <<
- typeName << "::" << method << " cancels reentry.");
+ debugs(93, 5, HERE << typeName << "::" << inCall <<
+ " is in progress; " << typeName << "::" << method <<
+ " cancels reentry.");
+ return false;
+ }
+
+ if (!self) {
+ // this may happen when swanSong() has not properly cleaned up.
+ debugs(93, 5, HERE << typeName << "::" << method <<
+ " is not admitted to a finished transaction " << this);
return false;
}
void ICAPXaction::callException(const TextException &e)
{
- debugs(93, 4, typeName << "::" << inCall << " caught an exception: " <<
+ debugs(93, 2, typeName << "::" << inCall << " caught an exception: " <<
e.message << ' ' << status());
reuseConnection = false; // be conservative
void ICAPXaction::callEnd()
{
if (done()) {
- debugs(93, 5, HERE << "ICAPXaction::" << inCall << " ends xaction " <<
- status());
- doStop(); // may delete us
+ debugs(93, 5, typeName << "::" << inCall << " ends xaction " <<
+ status());
+ swanSong();
+ const char *inCallSaved = inCall;
+ const char *typeNameSaved = typeName;
+ inCall = NULL;
+ self = NULL; // will delete us, now or eventually
+ debugs(93, 6, HERE << typeNameSaved << "::" << inCallSaved <<
+ " ended " << this);
return;
} else
if (doneWithIo()) {
- debugs(93, 5, HERE << typeName << " done with I/O " << status());
+ debugs(93, 5, HERE << typeName << " done with I/O" << status());
closeConnection();
}
- debugs(93, 6, typeName << "::" << inCall << " ended " << status());
+ debugs(93, 6, typeName << "::" << inCall << " ended" << status());
inCall = NULL;
}
static MemBuf buf;
buf.reset();
- buf.append("[", 1);
+ buf.append(" [", 2);
fillPendingStatus(buf);
buf.append("/", 1);
fillDoneStatus(buf);
- buf.append("]", 1);
+ buf.Printf(" icapx%d]", id);
buf.terminate();
void ICAPXaction::fillPendingStatus(MemBuf &buf) const
{
if (connection >= 0) {
- buf.Printf("Comm(%d", connection);
+ buf.Printf("FD %d", connection);
if (writer)
buf.append("w", 1);
if (reader)
buf.append("r", 1);
- buf.append(")", 1);
+ buf.append(";", 1);
}
}
/*
- * $Id: ICAPXaction.h,v 1.9 2006/10/31 23:30:58 wessels Exp $
+ * $Id: ICAPXaction.h,v 1.10 2007/04/06 04:50:08 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
#include "comm.h"
#include "MemBuf.h"
#include "ICAPServiceRep.h"
+#include "AsyncCall.h"
class HttpMsg;
-
class TextException;
-/* The ICAP Xaction implements message pipe sink and source interfaces. It
- * receives virgin HTTP messages, communicates with the ICAP server, and sends
- * the adapted messages back. ICAPClient is the "owner" of the ICAPXaction. */
+/*
+ * The ICAP Xaction implements common tasks for ICAP OPTIONS, REQMOD, and
+ * RESPMOD transactions.
+ *
+ * All ICAP transactions are refcounted and hold a pointer to self.
+ * Both are necessary because a user need to access transaction data
+ * after the transaction has finished, while a transaction may need to
+ * finish after all its explicit users are gone. For safety and simplicity,
+ * the code assumes that both cases can happen to any ICAP transaction.
+ */
// Note: ICAPXaction must be the first parent for object-unaware cbdata to work
public:
typedef RefCount<ICAPXaction> Pointer;
+ // Use this to start ICAP transactions because they need a pointer
+ // to self and because the start routine may result in failures/callbacks.
+ static ICAPXaction *AsyncStart(ICAPXaction *x);
+
public:
ICAPXaction(const char *aTypeName);
virtual ~ICAPXaction();
void noteCommTimedout();
void noteCommClosed();
+ // start handler, treat as protected and call it from the kids
+ virtual void start() = 0;
+ AsyncCallWrapper(93,3, ICAPXaction, start);
+
protected:
// Set or get service pointer; ICAPXaction cbdata-locks it.
void service(ICAPServiceRep::Pointer &aService);
bool done() const;
virtual bool doneAll() const;
- virtual void doStop();
void mustStop(const char *reason);
+ // called just before the 'done' transaction is deleted
+ virtual void swanSong();
+
// returns a temporary string depicting transaction status, for debugging
const char *status() const;
virtual void fillPendingStatus(MemBuf &buf) const;
virtual bool fillVirginHttpHeader(MemBuf&) const;
protected:
+ Pointer self; // see comments in the class description above
+ const int id; // transaction ID for debugging, unique across ICAP xactions
+
int connection; // FD of the ICAP server connection
/*
const char *typeName; // the type of the final class (child), for debugging
private:
+ static int TheLastId;
ICAPServiceRep::Pointer theService;
const char *inCall; // name of the asynchronous call being executed, if any
+++ /dev/null
-#include "squid.h"
-#include "MsgPipe.h"
-#include "MsgPipeSource.h"
-#include "MsgPipeSink.h"
-#include "MsgPipeData.h"
-
-CBDATA_CLASS_INIT(MsgPipe);
-
-// static event callback template
-// XXX: refcounting needed to make sure destination still exists
-#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); \
-}
-
-// static event callbacks
-MsgPipe_MAKE_CALLBACK(SourceStart, sink)
-MsgPipe_MAKE_CALLBACK(SourceProgress, sink)
-MsgPipe_MAKE_CALLBACK(SourceFinish, sink)
-MsgPipe_MAKE_CALLBACK(SourceAbort, sink)
-MsgPipe_MAKE_CALLBACK(SinkNeed, source)
-MsgPipe_MAKE_CALLBACK(SinkAbort, source)
-
-
-MsgPipe::MsgPipe(const char *aName): name(aName),
- data(NULL), source(NULL), sink(NULL)
-{}
-
-MsgPipe::~MsgPipe()
-{
- delete data;
- assert(source == NULL);
- assert(sink == NULL);
-};
-
-void MsgPipe::sendSourceStart()
-{
- debug(99,5)("MsgPipe::sendSourceStart() called\n");
- sendLater("SourceStart", &MsgPipe_sendSourceStart, sink);
-}
-
-
-
-void MsgPipe::sendSourceProgress()
-{
- debug(99,5)("MsgPipe::sendSourceProgress() called\n");
- sendLater("SourceProgress", &MsgPipe_sendSourceProgress, sink);
-}
-
-void MsgPipe::sendSourceFinish()
-{
- debug(99,5)("MsgPipe::sendSourceFinish() called\n");
- sendLater("sendSourceFinish", &MsgPipe_sendSourceFinish, sink);
- source = NULL;
-}
-
-void MsgPipe::sendSourceAbort()
-{
- debug(99,5)("MsgPipe::sendSourceAbort() called\n");
- sendLater("SourceAbort", &MsgPipe_sendSourceAbort, sink);
- source = NULL;
-}
-
-
-void MsgPipe::sendSinkNeed()
-{
- debug(99,5)("MsgPipe::sendSinkNeed() called\n");
- sendLater("SinkNeed", &MsgPipe_sendSinkNeed, source);
-}
-
-void MsgPipe::sendSinkAbort()
-{
- debug(99,5)("MsgPipe::sendSinkAbort() called\n");
- sendLater("SinkAbort", &MsgPipe_sendSinkAbort, source);
- sink = NULL;
-}
-
-void MsgPipe::sendLater(const char *callName, EVH * handler, MsgPipeEnd *destination)
-{
- if (canSend(destination, callName, true))
- eventAdd(callName, handler, this, 0.0, 0, true);
-}
-
-bool MsgPipe::canSend(MsgPipeEnd *destination, const char *callName, bool future)
-{
- const bool res = destination != NULL;
- const char *verb = future ?
- (res ? "will send " : "wont send ") :
- (res ? "sends " : "ignores ");
- debugs(93,5, "MsgPipe " << name << "(" << this << ") " <<
- verb << callName << " to the " <<
- (destination ? destination->kind() : "destination") << "(" <<
- destination << "); " <<
- "data: " << data << "; source: " << source << "; sink " << sink);
- return res;
-}
+++ /dev/null
-
-/*
- * $Id: MsgPipe.h,v 1.5 2006/08/21 00:50:45 robertc 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_MSGPIPE_H
-#define SQUID_MSGPIPE_H
-
-#include "cbdata.h"
-#include "event.h"
-
-// MsgPipe is a unidirectional communication channel for asynchronously
-// transmitting potentially large messages. It aggregates the message
-// being piped and pointers to the message sender and recepient.
-// MsgPipe also provides convenience wrappers for asynchronous calls to
-// recepient's and sender's note*() methods.
-
-class MsgPipeData;
-
-class MsgPipeEnd;
-
-class MsgPipeSource;
-
-class MsgPipeSink;
-
-class MsgPipe : public RefCountable
-{
-
-public:
- typedef RefCount<MsgPipe> Pointer;
-
- MsgPipe(const char *aName = "anonym");
- ~MsgPipe();
-
- // the pipe source calls these to notify the sink
- void sendSourceStart();
- void sendSourceProgress();
- void sendSourceFinish();
- void sendSourceAbort();
-
- // the pipe sink calls these to notify the source
- void sendSinkNeed();
- void sendSinkAbort();
-
- // private method exposed for the event handler only
- bool canSend(MsgPipeEnd *destination, const char *callName, bool future);
-
-public:
- const char *name; // unmanaged pointer used for debugging only
-
- MsgPipeData *data;
- MsgPipeSource *source;
- MsgPipeSink *sink;
-
-private:
- void sendLater(const char *callName, EVH * handler, MsgPipeEnd *destination);
-
- CBDATA_CLASS2(MsgPipe);
-};
-
-#endif /* SQUID_MSGPIPE_H */
+++ /dev/null
-
-/*
- * $Id: MsgPipeEnd.h,v 1.2 2005/11/21 23:46:27 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_MSGPIPEEND_H
-#define SQUID_MSGPIPEEND_H
-
-// MsgPipeEnd is a common part of the MsgPipeSource and MsgPipeSink interfaces.
-// Mesage pipe ends must be refcounted so that the recepient does not disappear
-// while a message is being [asynchoronously] delivered to it.
-
-class MsgPipeEnd: public RefCountable
-{
-
-public:
- virtual ~MsgPipeEnd() {}
-
- virtual const char *kind() const = 0; // "sink" or "source", for debugging
-};
-
-#endif /* SQUID_MSGPIPEEND_H */
+++ /dev/null
-
-/*
- * $Id: MsgPipeSource.h,v 1.3 2005/12/22 22:26:31 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_MSGPIPESOURCE_H
-#define SQUID_MSGPIPESOURCE_H
-
-#include "MsgPipeEnd.h"
-
-// MsgPipeSource is an interface for the sender of a given message
-// over a given message pipe. Use MsgPipe to call source methods.
-
-class MsgPipe;
-
-class MsgPipeSource: public MsgPipeEnd
-{
-
-public:
- virtual const char *kind() const { return "source"; }
-
- virtual void noteSinkNeed(MsgPipe *p) = 0;
- virtual void noteSinkAbort(MsgPipe *p) = 0;
-};
-
-#endif /* SQUID_MSGPIPESOURCE_H */
#
# Makefile for the Squid Object Cache server
#
-# $Id: Makefile.am,v 1.175 2007/02/25 11:41:22 hno Exp $
+# $Id: Makefile.am,v 1.176 2007/04/06 04:50:04 rousskov Exp $
#
# Uncomment and customize the following to suit your needs:
#
ACLChecklist.h \
$(squid_ACLSOURCES) \
asn.cc \
+ AsyncCall.cc \
+ AsyncCall.h \
AsyncEngine.cc \
AsyncEngine.h \
authenticate.cc \
client_side_reply.h \
client_side_request.cc \
client_side_request.h \
- BodyReader.cc \
- BodyReader.h \
+ BodyPipe.cc \
+ BodyPipe.h \
ClientRequestContext.h \
clientStream.cc \
clientStream.h \
ICAP/ChunkedCodingParser.h \
ICAP/ICAPClient.cc \
ICAP/ICAPClient.h \
- ICAP/ICAPClientVector.cc \
- ICAP/ICAPClientVector.h \
- ICAP/ICAPClientReqmodPrecache.cc \
- ICAP/ICAPClientReqmodPrecache.h \
- ICAP/ICAPClientRespmodPrecache.cc \
- ICAP/ICAPClientRespmodPrecache.h \
+ ICAP/ICAPInitiator.cc \
+ ICAP/ICAPInitiator.h \
+ ICAP/ICAPInOut.h \
ICAP/ICAPConfig.cc \
ICAP/ICAPConfig.h \
ICAP/ICAPElements.cc \
ICAP/ICAPServiceRep.h \
ICAP/ICAPXaction.cc \
ICAP/ICAPXaction.h \
- ICAP/MsgPipe.cc \
- ICAP/MsgPipe.h \
- ICAP/MsgPipeData.h \
- ICAP/MsgPipeEnd.h \
- ICAP/MsgPipeSink.h \
- ICAP/MsgPipeSource.h \
ICAP/TextException.cc \
ICAP/TextException.h
time.cc \
ufsdump.cc \
url.cc \
- BodyReader.cc \
+ AsyncCall.cc \
+ AsyncCall.h \
+ BodyPipe.cc \
+ BodyPipe.h \
ConfigParser.cc \
store.cc \
StoreFileSystem.cc \
ACLRegexData.cc \
ACLUserData.cc \
authenticate.cc \
- BodyReader.cc \
+ BodyPipe.cc \
cache_manager.cc \
cache_cf.cc \
CacheDigest.cc \
ACLRegexData.cc \
ACLUserData.cc \
authenticate.cc \
- BodyReader.cc \
+ BodyPipe.cc \
cache_manager.cc \
cache_cf.cc \
CacheDigest.cc \
ACLRegexData.cc \
ACLUserData.cc \
authenticate.cc \
- BodyReader.cc \
+ BodyPipe.cc \
cache_manager.cc \
cache_cf.cc \
CacheDigest.cc \
ACLRegexData.cc \
ACLUserData.cc \
authenticate.cc \
- BodyReader.cc \
+ BodyPipe.cc \
cache_cf.cc \
cache_manager.cc \
CacheDigest.cc \
ACLRegexData.cc \
ACLUserData.cc \
authenticate.cc \
- BodyReader.cc \
+ BodyPipe.cc \
cache_manager.cc \
cache_cf.cc \
CacheDigest.cc \
ACLRegexData.cc \
ACLUserData.cc \
authenticate.cc \
- BodyReader.cc \
+ BodyPipe.cc \
cache_manager.cc \
cache_cf.cc \
CacheDigest.cc \
/*
- * $Id: Server.cc,v 1.7 2006/10/31 23:30:56 wessels Exp $
+ * $Id: Server.cc,v 1.8 2007/04/06 04:50:05 rousskov Exp $
*
* DEBUG:
* AUTHOR: Duane Wessels
#include "Store.h"
#include "HttpRequest.h"
#include "HttpReply.h"
-#if ICAP_CLIENT
-#include "ICAP/ICAPClientRespmodPrecache.h"
-#endif
+#include "errorpage.h"
+
-ServerStateData::ServerStateData(FwdState *theFwdState)
+ServerStateData::ServerStateData(FwdState *theFwdState): requestSender(NULL)
{
fwd = theFwdState;
entry = fwd->entry;
- entry->lock()
+ entry->lock();
- ;
request = HTTPMSGLOCK(fwd->request);
}
fwd = NULL; // refcounted
+ if (requestBodySource != NULL)
+ requestBodySource->clearConsumer();
+
+#if ICAP_CLIENT
+ cleanIcap();
+#endif
+}
+
+// called when no more server communication is expected; may quit
+void
+ServerStateData::serverComplete()
+{
+ debugs(11,5,HERE << "serverComplete " << this);
+
+ if (!doneWithServer()) {
+ closeServer();
+ assert(doneWithServer());
+ }
+
+ if (requestBodySource != NULL)
+ stopConsumingFrom(requestBodySource);
+
+#if ICAP_CLIENT
+ if (virginBodyDestination != NULL)
+ stopProducingFor(virginBodyDestination, true);
+
+ if (!doneWithIcap())
+ return;
+#endif
+
+ completeForwarding();
+ quitIfAllDone();
+}
+
+// When we are done talking to the primary server, we may be still talking
+// to the ICAP service. And vice versa. Here, we quit only if we are done
+// talking to both.
+void ServerStateData::quitIfAllDone() {
+#if ICAP_CLIENT
+ if (!doneWithIcap()) {
+ debugs(11,5, HERE << "transaction not done: still talking to ICAP");
+ return;
+ }
+#endif
+
+ if (!doneWithServer()) {
+ debugs(11,5, HERE << "transaction not done: still talking to server");
+ return;
+ }
+
+ debugs(11,3, HERE << "transaction done");
+ delete this;
+}
+
+// FTP side overloads this to work around multiple calls to fwd->complete
+void
+ServerStateData::completeForwarding() {
+ debugs(11,5, HERE << "completing forwarding for " << fwd);
+ assert(fwd != NULL);
+ fwd->complete();
+}
+
+// Entry-dependent callbacks use this check to quit if the entry went bad
+bool
+ServerStateData::abortOnBadEntry(const char *abortReason)
+{
+ if (entry->isAccepting())
+ return false;
+
+ debugs(11,5, HERE << "entry is not Accepting!");
+ abortTransaction(abortReason);
+ return true;
+}
+
+// more request or adapted response body is available
+void
+ServerStateData::noteMoreBodyDataAvailable(BodyPipe &bp)
+{
+#if ICAP_CLIENT
+ if (adaptedBodySource == &bp) {
+ handleMoreAdaptedBodyAvailable();
+ return;
+ }
+#endif
+ handleMoreRequestBodyAvailable();
+}
+
+// the entire request or adapted response body was provided, successfully
+void
+ServerStateData::noteBodyProductionEnded(BodyPipe &bp)
+{
#if ICAP_CLIENT
- if (icap) {
- debug(11,5)("ServerStateData destroying icap=%p\n", icap);
- icap->ownerAbort();
- delete icap;
+ if (adaptedBodySource == &bp) {
+ handleAdaptedBodyProductionEnded();
+ return;
}
#endif
+ handleRequestBodyProductionEnded();
+}
+
+// premature end of the request or adapted response body production
+void
+ServerStateData::noteBodyProducerAborted(BodyPipe &bp)
+{
+#if ICAP_CLIENT
+ if (adaptedBodySource == &bp) {
+ handleAdaptedBodyProducerAborted();
+ return;
+ }
+#endif
+ handleRequestBodyProducerAborted();
+}
+
+
+// more origin request body data is available
+void
+ServerStateData::handleMoreRequestBodyAvailable()
+{
+ if (!requestSender)
+ sendMoreRequestBody();
+ else
+ debugs(9,3, HERE << "waiting for request body write to complete");
+}
+
+// there will be no more handleMoreRequestBodyAvailable calls
+void
+ServerStateData::handleRequestBodyProductionEnded()
+{
+ if (!requestSender)
+ doneSendingRequestBody();
+ else
+ debugs(9,3, HERE << "waiting for request body write to complete");
+}
+
+// called when we are done sending request body; kids extend this
+void
+ServerStateData::doneSendingRequestBody() {
+ debugs(9,3, HERE << "done sending request body");
+ assert(requestBodySource != NULL);
+ stopConsumingFrom(requestBodySource);
+
+ // kids extend this
+}
+
+// called when body producers aborts; kids extend this
+void
+ServerStateData::handleRequestBodyProducerAborted()
+{
+ if (requestSender != NULL)
+ debugs(9,3, HERE << "fyi: request body aborted while we were sending");
+
+ stopConsumingFrom(requestBodySource); // requestSender, if any, will notice
+
+ // kids extend this
+}
+
+void
+ServerStateData::sentRequestBodyWrapper(int fd, char *bufnotused, size_t size, comm_err_t errflag, int xerrno, void *data)
+{
+ ServerStateData *server = static_cast<ServerStateData *>(data);
+ server->sentRequestBody(fd, size, errflag);
+}
+
+// called when we wrote request headers(!) or a part of the body
+void
+ServerStateData::sentRequestBody(int fd, size_t size, comm_err_t errflag)
+{
+ debug(11, 5) ("sentRequestBody: FD %d: size %d: errflag %d.\n",
+ fd, (int) size, errflag);
+ debugs(32,3,HERE << "sentRequestBody called");
+
+ requestSender = NULL;
+
+ if (size > 0) {
+ fd_bytes(fd, size, FD_WRITE);
+ kb_incr(&statCounter.server.all.kbytes_out, size);
+ // kids should increment their counters
+ }
+
+ if (errflag == COMM_ERR_CLOSING)
+ return;
+
+ if (!requestBodySource) {
+ debugs(9,3, HERE << "detected while-we-were-sending abort");
+ return; // do nothing;
+ }
+
+ if (errflag) {
+ debug(11, 1) ("sentRequestBody error: FD %d: %s\n", fd, xstrerr(errno));
+ ErrorState *err;
+ err = errorCon(ERR_WRITE_ERROR, HTTP_BAD_GATEWAY, fwd->request);
+ err->xerrno = errno;
+ fwd->fail(err);
+ abortTransaction("I/O error while sending request body");
+ return;
+ }
+
+ if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+ abortTransaction("store entry aborted while sending request body");
+ return;
+ }
+
+ if (requestBodySource->exhausted())
+ doneSendingRequestBody();
+ else
+ sendMoreRequestBody();
}
+void
+ServerStateData::sendMoreRequestBody()
+{
+ assert(requestBodySource != NULL);
+ assert(!requestSender);
+ MemBuf buf;
+ if (requestBodySource->getMoreData(buf)) {
+ debugs(9,3, HERE << "will write " << buf.contentSize() << " request body bytes");
+ requestSender = &ServerStateData::sentRequestBodyWrapper;
+ comm_write_mbuf(dataDescriptor(), &buf, requestSender, this);
+ } else {
+ debugs(9,3, HERE << "will wait for more request body bytes or eof");
+ requestSender = NULL;
+ }
+}
+
+// called by noteIcapHeadersAdapted(), HTTP server overwrites this
+void
+ServerStateData::haveParsedReplyHeaders()
+{
+ // default does nothing
+}
+
+
#if ICAP_CLIENT
/*
* Initiate an ICAP transaction. Return true on success.
* or take other action.
*/
bool
-ServerStateData::startIcap(ICAPServiceRep::Pointer service)
+ServerStateData::startIcap(ICAPServiceRep::Pointer service, HttpRequest *cause)
{
debug(11,5)("ServerStateData::startIcap() called\n");
if (!service) {
debug(11,3)("ServerStateData::startIcap fails: broken service\n");
return false;
}
- assert(NULL == icap);
- icap = new ICAPClientRespmodPrecache(service);
+
+ // check whether we should be sending a body as well
+ assert(!virginBodyDestination);
+ assert(!reply->body_pipe);
+ // start body pipe to feed ICAP transaction if needed
+ ssize_t size = 0;
+ if (reply->expectingBody(cause->method, size) && size) {
+ virginBodyDestination = new BodyPipe(this);
+ reply->body_pipe = virginBodyDestination;
+ debugs(93, 6, HERE << "will send virgin reply body to " <<
+ virginBodyDestination << "; size: " << size);
+ }
+
+ adaptedHeadSource = new ICAPModXact(this, reply, cause, service);
+ ICAPModXact::AsyncStart(adaptedHeadSource.getRaw());
return true;
}
+// properly cleans up ICAP-related state
+// may be called multiple times
+void ServerStateData::cleanIcap() {
+ debugs(11,5, HERE << "cleaning ICAP");
+
+ if (virginBodyDestination != NULL)
+ stopProducingFor(virginBodyDestination, false);
+
+ if (adaptedHeadSource != NULL) {
+ AsyncCall(11,5, adaptedHeadSource.getRaw(), ICAPModXact::noteInitiatorAborted);
+ adaptedHeadSource = NULL;
+ }
+
+ if (adaptedBodySource != NULL)
+ stopConsumingFrom(adaptedBodySource);
+
+ assert(doneWithIcap()); // make sure the two methods are in sync
+}
+
+bool
+ServerStateData::doneWithIcap() const {
+ return !virginBodyDestination && !adaptedHeadSource && !adaptedBodySource;
+}
+
+// can supply more virgin response body data
+void
+ServerStateData::noteMoreBodySpaceAvailable(BodyPipe &)
+{
+ maybeReadVirginBody();
+}
+
+// the consumer of our virgin response body aborted, we should too
+void
+ServerStateData::noteBodyConsumerAborted(BodyPipe &bp)
+{
+ stopProducingFor(virginBodyDestination, false);
+ handleIcapAborted();
+}
+
+// received adapted response headers (body may follow)
+void
+ServerStateData::noteIcapHeadersAdapted()
+{
+ // extract and lock reply before (adaptedHeadSource = NULL) can destroy it
+ HttpReply *rep = dynamic_cast<HttpReply*>(adaptedHeadSource->adapted.header);
+ HTTPMSGLOCK(rep);
+ adaptedHeadSource = NULL; // we do not expect any more messages from it
+
+ if (abortOnBadEntry("entry went bad while waiting for adapted headers")) {
+ HTTPMSGUNLOCK(rep); // hopefully still safe, even if "this" is deleted
+ return;
+ }
+
+ assert(rep);
+ entry->replaceHttpReply(rep);
+ HTTPMSGUNLOCK(reply);
+
+ reply = rep; // already HTTPMSGLOCKed above
+
+ haveParsedReplyHeaders();
+
+ assert(!adaptedBodySource);
+ if (reply->body_pipe != NULL) {
+ // subscribe to receive adapted body
+ adaptedBodySource = reply->body_pipe;
+ // assume that ICAP does not auto-consume on failures
+ assert(adaptedBodySource->setConsumerIfNotLate(this));
+ } else {
+ // no body
+ handleIcapCompleted();
+ }
+
+}
+
+// will not receive adapted response headers (and, hence, body)
+void
+ServerStateData::noteIcapHeadersAborted()
+{
+ adaptedHeadSource = NULL;
+ handleIcapAborted();
+}
+
+// more adapted response body is available
+void
+ServerStateData::handleMoreAdaptedBodyAvailable()
+{
+ const size_t contentSize = adaptedBodySource->buf().contentSize();
+
+ debugs(11,5, HERE << "consuming " << contentSize << " bytes of adapted " <<
+ "response body at offset " << adaptedBodySource->consumedSize());
+
+ if (abortOnBadEntry("entry refuses adapted body"))
+ return;
+
+ assert(entry);
+ BodyPipeCheckout bpc(*adaptedBodySource);
+ const StoreIOBuffer ioBuf(&bpc.buf, bpc.offset);
+ entry->write(ioBuf);
+ bpc.buf.consume(contentSize);
+ bpc.checkIn();
+}
+
+// the entire adapted response body was produced, successfully
+void
+ServerStateData::handleAdaptedBodyProductionEnded()
+{
+ stopConsumingFrom(adaptedBodySource);
+
+ if (abortOnBadEntry("entry went bad while waiting for adapted body eof"))
+ return;
+
+ handleIcapCompleted();
+}
+
+// premature end of the adapted response body
+void ServerStateData::handleAdaptedBodyProducerAborted()
+{
+ stopConsumingFrom(adaptedBodySource);
+ handleIcapAborted();
+}
+
+// common part of noteIcapHeadersAdapted and handleAdaptedBodyProductionEnded
+void
+ServerStateData::handleIcapCompleted()
+{
+ debugs(11,5, HERE << "handleIcapCompleted");
+ cleanIcap();
+ completeForwarding();
+ quitIfAllDone();
+}
+
+// common part of noteIcap*Aborted and noteBodyConsumerAborted methods
+void
+ServerStateData::handleIcapAborted()
+{
+ debugs(11,5, HERE << "handleIcapAborted; entry empty: " << entry->isEmpty());
+
+ if (abortOnBadEntry("entry went bad while ICAP aborted"))
+ return;
+
+ if (entry->isEmpty()) {
+ debugs(11,9, HERE << "creating ICAP error entry after ICAP failure");
+ ErrorState *err =
+ errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
+ err->xerrno = errno;
+ fwd->fail(err);
+ fwd->dontRetry(true);
+ }
+
+ debugs(11,5, HERE << "bailing after ICAP failure");
+
+ cleanIcap();
+ closeServer();
+ quitIfAllDone();
+}
+
#endif
/*
- * $Id: Server.h,v 1.2 2006/10/31 23:30:56 wessels Exp $
+ * $Id: Server.h,v 1.3 2007/04/06 04:50:05 rousskov Exp $
*
* AUTHOR: Duane Wessels
*
*/
/*
- * ServerStateData is a class for common elements of Server-side modules
- * such as http.cc and ftp.cc. It was invented to make ICAP code simpler.
+ * ServerStateData is a common base for server-side classes such as
+ * HttpStateData and FtpStateData. All such classes must be able to
+ * consume request bodies from the client-side or ICAP producer, adapt
+ * virgin responses using ICAP, and provide the client-side consumer with
+ * responses.
+ *
+ * TODO: Rename to ServerStateDataInfoRecordHandler.
*/
+
#ifndef SQUID_SERVER_H
#define SQUID_SERVER_H
#include "StoreIOBuffer.h"
#include "forward.h"
+#include "BodyPipe.h"
#if ICAP_CLIENT
#include "ICAP/ICAPServiceRep.h"
-
-class ICAPClientRespmodPrecache;
+#include "ICAP/ICAPInitiator.h"
+#include "ICAP/ICAPModXact.h"
class ICAPAccessCheck;
#endif
-class ServerStateData
+class ServerStateData:
+#if ICAP_CLIENT
+ public ICAPInitiator,
+ public BodyProducer,
+#endif
+ public BodyConsumer
{
public:
ServerStateData(FwdState *);
virtual ~ServerStateData();
+ // returns primary or "request data connection" fd
+ virtual int dataDescriptor() const = 0;
+
+ // BodyConsumer: consume request body or adapted response body.
+ // The implementation just calls the corresponding HTTP or ICAP handle*()
+ // method, depending on the pipe.
+ virtual void noteMoreBodyDataAvailable(BodyPipe &);
+ virtual void noteBodyProductionEnded(BodyPipe &);
+ virtual void noteBodyProducerAborted(BodyPipe &);
+
+ // read response data from the network
+ virtual void maybeReadVirginBody() = 0;
+
+ // abnormal transaction termination; reason is for debugging only
+ virtual void abortTransaction(const char *reason) = 0;
+
#if ICAP_CLIENT
- 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;
+
+ // ICAPInitiator: start an ICAP transaction and receive adapted headers.
+ virtual void noteIcapHeadersAdapted();
+ virtual void noteIcapHeadersAborted();
+
+ // BodyProducer: provide virgin response body to ICAP.
+ virtual void noteMoreBodySpaceAvailable(BodyPipe &);
+ virtual void noteBodyConsumerAborted(BodyPipe &);
#endif
-public:
- // should be protected
+public: // should be protected
+ void serverComplete(); // call when no server communication is expected
+
+protected:
+ // kids customize these
+ virtual void haveParsedReplyHeaders(); // default does nothing
+ virtual void completeForwarding(); // default calls fwd->complete()
+
+ // BodyConsumer for HTTP: consume request body.
+ void handleMoreRequestBodyAvailable();
+ void handleRequestBodyProductionEnded();
+ virtual void handleRequestBodyProducerAborted() = 0;
+
+ // sending of the request body to the server
+ void sendMoreRequestBody();
+ // has body; kids overwrite to increment I/O stats counters
+ virtual void sentRequestBody(int fd, size_t size, comm_err_t errflag) = 0;
+ virtual void doneSendingRequestBody() = 0;
+ static IOCB sentRequestBodyWrapper;
+
+ virtual void closeServer() = 0; // end communication with the server
+ virtual bool doneWithServer() const = 0; // did we end communication?
+
+ // Entry-dependent callbacks use this check to quit if the entry went bad
+ bool abortOnBadEntry(const char *abortReason);
+
+#if ICAP_CLIENT
+ bool startIcap(ICAPServiceRep::Pointer, HttpRequest *cause);
+ void cleanIcap();
+ virtual bool doneWithIcap() const; // did we end ICAP communication?
+
+ // BodyConsumer for ICAP: consume adapted response body.
+ void handleMoreAdaptedBodyAvailable();
+ void handleAdaptedBodyProductionEnded();
+ void handleAdaptedBodyProducerAborted();
+
+ void handleIcapCompleted();
+ void handleIcapAborted();
+#endif
+
+public: // should not be
StoreEntry *entry;
FwdState::Pointer fwd;
HttpRequest *request;
HttpReply *reply;
protected:
+ BodyPipe::Pointer requestBodySource; // to consume request body
+ IOCB *requestSender; // set if we are expecting comm_write to call us back
+
#if ICAP_CLIENT
+ BodyPipe::Pointer virginBodyDestination; // to provide virgin response body
+ ICAPModXact::Pointer adaptedHeadSource; // to get adapted response headers
+ BodyPipe::Pointer adaptedBodySource; // to consume adated response body
- ICAPClientRespmodPrecache *icap;
bool icapAccessCheckPending;
- bool startIcap(ICAPServiceRep::Pointer);
#endif
+private:
+ void quitIfAllDone(); // successful termination
+
};
#endif /* SQUID_SERVER_H */
#
-# $Id: cf.data.pre,v 1.429 2007/02/25 11:32:29 hno Exp $
+# $Id: cf.data.pre,v 1.430 2007/04/06 04:50:05 rousskov Exp $
#
#
# SQUID Web Proxy Cache http://www.squid-cache.org/
If you want to enable the ICAP module support, set this to on.
DOC_END
+NAME: icap_service_failure_limit
+TYPE: int
+IFDEF: ICAP_CLIENT
+LOC: TheICAPConfig.service_failure_limit
+DEFAULT: 10
+DOC_START
+ The limit specifies the number of failures that Squid tolerates
+ when establishing a new TCP connection with an ICAP service. If
+ the number of failures exceeds the limit, the ICAP service is
+ not used for new ICAP requests until it is time to refresh its
+ OPTIONS. The per-service failure counter is reset to zero each
+ time Squid fetches new service OPTIONS.
+
+ A negative value disables the limit. Without the limit, an ICAP
+ service will not be considered down due to connectivity failures
+ between ICAP OPTIONS requests.
+DOC_END
+
+NAME: icap_service_revival_delay
+TYPE: int
+IFDEF: ICAP_CLIENT
+LOC: TheICAPConfig.service_revival_delay
+DEFAULT: 180
+DOC_START
+ The delay specifies the number of seconds to wait after an ICAP
+ OPTIONS request failure before requesting the options again. The
+ failed ICAP service is considered "down" until fresh OPTIONS are
+ fetched.
+
+ The actual delay cannot be smaller than the hardcoded minimum
+ delay of 60 seconds.
+DOC_END
+
NAME: icap_preview_enable
TYPE: onoff
IFDEF: ICAP_CLIENT
LOC: TheICAPConfig.send_client_username
DEFAULT: off
DOC_START
- This adds the header "X-Client-Username" to ICAP requests
- if proxy access is authentified.
+ This sends authenticated HTTP client username (if available) to
+ the ICAP service. The username value is encoded based on the
+ icap_client_username_encode option and is sent using the header
+ specified by the icap_client_username_header option.
+DOC_END
+
+NAME: icap_client_username_header
+TYPE: string
+IFDEF: ICAP_CLIENT
+LOC: TheICAPConfig.client_username_header
+DEFAULT: X-Client-Username
+DOC_START
+ ICAP request header name to use for send_client_username.
+DOC_END
+
+NAME: icap_client_username_encode
+TYPE: onoff
+IFDEF: ICAP_CLIENT
+COMMENT: on|off
+LOC: TheICAPConfig.client_username_encode
+DEFAULT: off
+DOC_START
+ Whether to base64 encode the authenticated client username.
DOC_END
NAME: icap_service
/*
- * $Id: client_side.cc,v 1.744 2006/10/31 23:30:56 wessels Exp $
+ * $Id: client_side.cc,v 1.745 2007/04/06 04:50:05 rousskov Exp $
*
* DEBUG: section 33 Client-side Routines
* AUTHOR: Duane Wessels
#if USE_IDENT
static IDCB clientIdentDone;
#endif
-static BodyReadFunc clientReadBody;
-static BodyAbortFunc clientAbortBody;
static CSCB clientSocketRecipient;
static CSD clientSocketDetach;
static void clientSetKeepaliveFlag(ClientHttpRequest *);
al.http.content_type = loggingEntry()->mem_obj->getReply()->content_type.buf();
}
+ debug(33, 9) ("clientLogRequest: http.code='%d'\n", al.http.code);
+
if (loggingEntry() && loggingEntry()->mem_obj)
al.cache.objectSize = contentLen(loggingEntry());
return true;
}
-BodyReader *
-ConnStateData::body_reader()
-{
- return body_reader_.getRaw();
-}
-
-void
-ConnStateData::body_reader(BodyReader::Pointer reader)
-{
- body_reader_ = reader;
-
- if (reader == NULL)
- fd_note(fd, "Waiting for next request");
- else
- fd_note(fd, "Reading request body");
-}
-
void
ConnStateData::freeAllContexts()
{
cbdataReferenceDone(port);
- body_reader(NULL); // refcounted
+ if (bodyPipe != NULL) {
+ bodyPipe->clearProducer(false);
+ bodyPipe = NULL; // refcounted
+ }
}
/*
}
void
-ClientSocketContext::initiateClose()
+ClientSocketContext::initiateClose(const char *reason)
{
+ debugs(33, 5, HERE << "initiateClose: closing for " << reason);
if (http != NULL) {
ConnStateData::Pointer conn = http->getConn();
if (conn != NULL) {
- if (conn->bodySizeLeft() > 0) {
- debug(33, 5) ("ClientSocketContext::initiateClose: closing, but first we need to read the rest of the request\n");
+ if (const ssize_t expecting = conn->bodySizeLeft()) {
+ debugs(33, 5, HERE << "ClientSocketContext::initiateClose: " <<
+ "closing, but first " << conn << " needs to read " <<
+ expecting << " request body bytes with " <<
+ conn->in.notYetUsed << " notYetUsed");
+
+ if (conn->closing()) {
+ debugs(33, 2, HERE << "avoiding double-closing " << conn);
+ return;
+ }
+
/*
* XXX We assume the reply fits in the TCP transmit
* window. If not the connection may stall while sending
* As of yet we have not received any complaints indicating
* this may be an issue.
*/
- conn->closing(true);
- /* any unread body becomes abortedSize at this point. */
- conn->in.abortedSize = conn->bodySizeLeft();
- /*
- * Trigger the BodyReader abort handler, if necessary,
- * by destroying it. It is a refcounted pointer, so
- * set it to NULL and let the destructor be called when
- * all references are gone.
- *
- * This seems to be flawed: theres no way this can trigger
- * if conn->body_reader is not NULL. Perhaps it works for
- * ICAP but not real requests ?
- */
- http->request->body_reader = NULL; // refcounted
+ conn->startClosing(reason);
return;
}
}
clientUpdateSocketStats(http->logType, size);
assert (this->fd() == fd);
+ /* Bail out quickly on COMM_ERR_CLOSING - close handlers will tidy up */
+ if (errflag == COMM_ERR_CLOSING)
+ return;
+
if (errflag || clientHttpRequestStatus(fd, http)) {
- debug (33,5)("clientWriteComplete: FD %d, closing connection due to failure, or true requeststatus\n", fd);
- initiateClose();
+ initiateClose("failure or true request status");
/* Do we leak here ? */
return;
}
/* fallthrough */
case STREAM_FAILED:
- initiateClose();
+ initiateClose("STREAM_UNPLANNED_COMPLETE|STREAM_FAILED");
return;
default:
r = HttpParserParseReqLine(hp);
if (r == 0) {
debug(33, 5) ("Incomplete request, waiting for end of request line\n");
- return NULL;
+ return NULL;
}
if (r == -1) {
return parseHttpRequestAbort(conn, "error:invalid-request");
/* Set method_p */
*method_p = HttpRequestMethod(&hp->buf[hp->m_start], &hp->buf[hp->m_end]);
if (*method_p == METHOD_NONE) {
- /* XXX need a way to say "this many character length string" */
+ /* XXX need a way to say "this many character length string" */
debug(33, 1) ("clientParseRequestMethod: Unsupported method in request '%s'\n", hp->buf);
- /* XXX where's the method set for this error? */
+ /* XXX where's the method set for this error? */
return parseHttpRequestAbort(conn, "error:unsupported-request-method");
}
if (strstr(req_hdr, "\r\r\n")) {
debug(33, 1) ("WARNING: suspicious HTTP request contains double CR\n");
- xfree(url);
+ xfree(url);
return parseHttpRequestAbort(conn, "error:double-CR");
}
{
ClientHttpRequest *http = context->http;
HttpRequest *request = NULL;
+ bool notedUseOfBuffer = false;
+
/* We have an initial client stream in place should it be needed */
/* setup our private context */
context->registerWithConn();
assert(context->http->out.offset == 0);
context->pullData();
conn->flags.readMoreRequests = false;
- goto finish;
+ goto finish;
}
if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) {
assert(context->http->out.offset == 0);
context->pullData();
conn->flags.readMoreRequests = false;
- goto finish;
+ goto finish;
}
/* compile headers */
assert(context->http->out.offset == 0);
context->pullData();
conn->flags.readMoreRequests = false;
- goto finish;
+ goto finish;
}
request->flags.accelerated = http->flags.accel;
assert(context->http->out.offset == 0);
context->pullData();
conn->flags.readMoreRequests = false;
- goto finish;
+ goto finish;
}
assert(context->http->out.offset == 0);
context->pullData();
conn->flags.readMoreRequests = false;
- goto finish;
+ goto finish;
}
http->request = HTTPMSGLOCK(request);
clientSetKeepaliveFlag(http);
- /* Do we expect a request-body? */
+ /* Do we expect a request-body? */
if (request->content_length > 0) {
- request->body_reader = new BodyReader(request->content_length,
- clientReadBody,
- clientAbortBody,
- NULL,
- conn.getRaw());
- conn->body_reader(request->body_reader);
- /*
- * NOTE: We haven't called connNoteUseOfBuffer() yet. It gets
- * done at finish: below. So here we have to subtract off
- * req_sz from notYetUsed, or else the BodyReader thinks it
- * has more data than it really does, and will get confused.
- */
- request->body_reader->notify(conn->in.notYetUsed - http->req_sz);
-
- if (request->body_reader->remaining())
+ request->body_pipe = conn->expectRequestBody(request->content_length);
+
+ // consume header early so that body pipe gets just the body
+ connNoteUseOfBuffer(conn.getRaw(), http->req_sz);
+ notedUseOfBuffer = true;
+
+ conn->handleRequestBodyData();
+ if (!request->body_pipe->exhausted())
conn->readSomeData();
/* Is it too large? */
assert(context->http->out.offset == 0);
context->pullData();
conn->flags.readMoreRequests = false;
- goto finish;
+ goto finish;
}
context->mayUseConnection(true);
http->doCallouts();
finish:
- /* Consume request buffer */
- connNoteUseOfBuffer(conn.getRaw(), http->req_sz);
+ if (!notedUseOfBuffer)
+ connNoteUseOfBuffer(conn.getRaw(), http->req_sz);
}
static void
ssize_t
ConnStateData::bodySizeLeft()
{
- if (body_reader_ != NULL)
- return body_reader_->remaining();
+ // XXX: this logic will not work for chunked requests with unknown sizes
+ if (bodyPipe != NULL)
+ return bodyPipe->unproducedSize();
return 0;
}
while (conn->in.notYetUsed > 0 && conn->bodySizeLeft() == 0) {
connStripBufferWhitespace (conn);
- /* Don't try to parse if the buffer is empty */
- if (conn->in.notYetUsed == 0)
- break;
+ /* Don't try to parse if the buffer is empty */
+ if (conn->in.notYetUsed == 0)
+ break;
/* Limit the number of concurrent requests to 2 */
/* Terminate the string */
conn->in.buf[conn->in.notYetUsed] = '\0';
- /* Begin the parsing */
- HttpParserInit(&hp, conn->in.buf, conn->in.notYetUsed);
+ /* Begin the parsing */
+ HttpParserInit(&hp, conn->in.buf, conn->in.notYetUsed);
/* Process request */
- PROF_start(parseHttpRequest);
+ PROF_start(parseHttpRequest);
context = parseHttpRequest(conn, &hp, &method, &http_ver);
- PROF_stop(parseHttpRequest);
+ PROF_stop(parseHttpRequest);
/* partial or incomplete request */
if (!context) {
if (size > 0) {
kb_incr(&statCounter.client_http.kbytes_in, size);
- char *current_buf = conn->in.addressToReadInto();
-
- if (buf != current_buf)
- xmemmove(current_buf, buf, size);
-
- conn->in.notYetUsed += size;
-
- conn->in.buf[conn->in.notYetUsed] = '\0'; /* Terminate the string */
+ conn->handleReadData(buf, size);
- /* if there is available non-aborted data, give it to the
- * BodyReader
- */
- if (conn->body_reader() != NULL)
- conn->body_reader()->notify(conn->in.notYetUsed);
-
- /* there is some aborted body to remove
- * could we? should we? use BodyReader to eliminate this via an
- * abort() api.
- *
- * This is not the most optimal path: ideally we would:
- * - optimise the memmove above to not move data we're discarding
- * - discard notYetUsed earlier
- */
- if (conn->in.abortedSize) {
- size_t discardSize = XMIN(conn->in.abortedSize, conn->in.notYetUsed);
- /* these figures must match */
- assert(conn->in.abortedSize == (size_t)conn->bodySizeLeft());
- conn->body_reader()->reduce_remaining(discardSize);
- connNoteUseOfBuffer(conn.getRaw(), discardSize);
- conn->in.abortedSize -= discardSize;
-
- if (!conn->in.abortedSize)
- /* we've finished reading like good clients,
- * now do the close that initiateClose initiated.
- *
- * XXX: do we have to close? why not check keepalive et.
- */
- comm_close(fd);
- }
} else if (size == 0) {
debug(33, 5) ("clientReadRequest: FD %d closed?\n", fd);
}
}
-/*
- * clientReadBody
- *
- * A request to receive some HTTP request body data. This is a
- * 'read_func' of BodyReader class. Feels to me like this function
- * belongs to ConnStateData class.
- *
- * clientReadBody is of type 'BodyReadFunc'
- */
-size_t
-clientReadBody(void *data, MemBuf &mb, size_t size)
+// called when new request data has been read from the socket
+void
+ConnStateData::handleReadData(char *buf, size_t size)
{
- ConnStateData *conn = (ConnStateData *) data;
- assert(conn);
- debugs(33,3,HERE << "clientReadBody requested size " << size);
- debugs(33,3,HERE << "clientReadBody FD " << conn->fd);
- debugs(33,3,HERE << "clientReadBody in.notYetUsed " << conn->in.notYetUsed);
+ char *current_buf = in.addressToReadInto();
- if (size > conn->in.notYetUsed)
- size = conn->in.notYetUsed; // may make size zero
+ if (buf != current_buf)
+ xmemmove(current_buf, buf, size);
- debugs(33,3,HERE << "clientReadBody actual size " << size);
-
- if (size > 0) {
- mb.append(conn->in.buf, size);
- connNoteUseOfBuffer(conn, size);
- }
+ in.notYetUsed += size;
+ in.buf[in.notYetUsed] = '\0'; /* Terminate the string */
- return size;
+ // if we are reading a body, stuff data into the body pipe
+ if (bodyPipe != NULL)
+ handleRequestBodyData();
}
-/*
- * clientAbortBody
- *
- * A dummy callback that consumes the remains of a request
- * body for an aborted transaction.
- *
- * clientAbortBody is of type 'BodyAbortFunc'
- */
-static void
-clientAbortBody(void *data, size_t remaining)
+// called when new request body data has been buffered in in.buf
+// may close the connection if we were closing and piped everything out
+void
+ConnStateData::handleRequestBodyData()
{
- ConnStateData *conn = (ConnStateData *) data;
- debugs(33,3,HERE << "clientAbortBody FD " << conn->fd);
- debugs(33,3,HERE << "clientAbortBody in.notYetUsed " << conn->in.notYetUsed);
- debugs(33,3,HERE << "clientAbortBody remaining " << remaining);
- conn->in.abortedSize += remaining;
+ assert(bodyPipe != NULL);
+
+ if (const size_t putSize = bodyPipe->putMoreData(in.buf, in.notYetUsed))
+ connNoteUseOfBuffer(this, putSize);
+
+ if (!bodyPipe->mayNeedMoreData()) {
+ // BodyPipe will clear us automagically when we produced everything
+ bodyPipe = NULL;
+
+ debugs(33,5, HERE << "produced entire request body for FD " << fd);
- if (conn->in.notYetUsed) {
- size_t to_discard = XMIN(conn->in.notYetUsed, conn->in.abortedSize);
- debugs(33,3,HERE << "to_discard " << to_discard);
- conn->in.abortedSize -= to_discard;
- connNoteUseOfBuffer(conn, to_discard);
+ if (closing()) {
+ /* we've finished reading like good clients,
+ * now do the close that initiateClose initiated.
+ *
+ * XXX: do we have to close? why not check keepalive et.
+ *
+ * XXX: To support chunked requests safely, we need to handle
+ * the case of an endless request. This if-statement does not,
+ * because mayNeedMoreData is true if request size is not known.
+ */
+ comm_close(fd);
+ }
}
+}
- /*
- * This assertion exists to make sure that there is never a
- * case where this function should be responsible for closing
- * the file descriptor.
- */
- assert(!conn->isOpen());
+void
+ConnStateData::noteMoreBodySpaceAvailable(BodyPipe &)
+{
+ handleRequestBodyData();
+}
+
+void
+ConnStateData::noteBodyConsumerAborted(BodyPipe &)
+{
+ if (!closing())
+ startClosing("body consumer aborted");
}
/* general lifetime handler for HTTP requests */
reading_ = newBool;
}
+
+BodyPipe::Pointer
+ConnStateData::expectRequestBody(size_t size)
+{
+ bodyPipe = new BodyPipe(this);
+ bodyPipe->setBodySize(size);
+ return bodyPipe;
+}
+
bool
ConnStateData::closing() const
{
return closing_;
}
+// Called by ClientSocketContext to give the connection a chance to read
+// the entire body before closing the socket.
void
-ConnStateData::closing(bool const newBool)
+ConnStateData::startClosing(const char *reason)
{
- assert (closing() != newBool);
- closing_ = newBool;
+ debugs(33, 5, HERE << "startClosing " << this << " for " << reason);
+ assert(!closing());
+ closing_ = true;
+
+ assert(bodyPipe != NULL);
+ assert(bodySizeLeft() > 0);
+
+ // We do not have to abort the body pipeline because we are going to
+ // read the entire body anyway.
+ // Perhaps an ICAP server wants to log the complete request.
+
+ // If a consumer abort have caused this closing, we may get stuck
+ // as nobody is consuming our data. Allow auto-consumption.
+ bodyPipe->enableAutoConsumption();
}
char *
/*
- * $Id: client_side.h,v 1.19 2006/10/26 19:42:24 serassio Exp $
+ * $Id: client_side.h,v 1.20 2007/04/06 04:50:06 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
#include "comm.h"
#include "StoreIOBuffer.h"
-#include "BodyReader.h"
+#include "BodyPipe.h"
#include "RefCount.h"
class ConnStateData;
void packRange(StoreIOBuffer const &, MemBuf * mb);
void deRegisterWithConn();
void doClose();
- void initiateClose();
+ void initiateClose(const char *reason);
bool mayUseConnection_; /* This request may use the connection. Don't read anymore requests for now */
bool connRegistered_;
};
/* A connection to a socket */
-class ConnStateData : public RefCountable
+class ConnStateData : public BodyProducer, public RefCountable
{
public:
char *buf;
size_t notYetUsed;
size_t allocatedSize;
- /*
- * abortedSize is the amount of data that should be read
- * from the socket and immediately discarded. It may be
- * set when there is a request body and that transaction
- * gets aborted. The client side should read the remaining
- * body content and just discard it, if the connection
- * will be staying open.
- */
- size_t abortedSize;
} in;
ssize_t bodySizeLeft();
void transparent(bool const);
bool reading() const;
void reading(bool const);
+
bool closing() const;
- void closing(bool const);
+ void startClosing(const char *reason);
- /* get the body reader that has been attached to the client
- * request
- */
- BodyReader * body_reader();
- /* set a body reader that should read data from the request
- */
- void body_reader(BodyReader::Pointer);
+ BodyPipe::Pointer expectRequestBody(size_t size);
+ virtual void noteMoreBodySpaceAvailable(BodyPipe &);
+ virtual void noteBodyConsumerAborted(BodyPipe &);
+
+ void handleReadData(char *buf, size_t size);
+ void handleRequestBodyData();
private:
CBDATA_CLASS2(ConnStateData);
bool reading_;
bool closing_;
Pointer openReference;
- BodyReader::Pointer body_reader_;
+ BodyPipe::Pointer bodyPipe; // set when we are reading request body
};
/* convenience class while splitting up body handling */
/*
- * $Id: client_side_request.cc,v 1.79 2007/02/25 11:32:32 hno Exp $
+ * $Id: client_side_request.cc,v 1.80 2007/04/06 04:50:06 rousskov Exp $
*
* DEBUG: section 85 Client-side Request Routines
* AUTHOR: Robert Collins (Originally Duane Wessels in client_side.c)
#include "wordlist.h"
#if ICAP_CLIENT
-#include "ICAP/ICAPClientReqmodPrecache.h"
+#include "ICAP/ICAPModXact.h"
#include "ICAP/ICAPElements.h"
#include "ICAP/ICAPConfig.h"
static void icapAclCheckDoneWrapper(ICAPServiceRep::Pointer service, void *data);
}
}
- debugs(85,3, HERE << this << " ClientHttpRequest destructed");
+ debugs(85,3, HERE << this << " ClientRequestContext destructed");
}
ClientRequestContext::ClientRequestContext(ClientHttpRequest *anHttp) : http(cbdataReference(anHttp)), acl_checklist (NULL), redirect_state (REDIRECT_NONE)
setConn(aConn);
dlinkAdd(this, &active, &ClientActiveRequests);
#if ICAP_CLIENT
-
request_satisfaction_mode = false;
#endif
}
{
debug(33, 3) ("httpRequestFree: %s\n", uri);
PROF_start(httpRequestFree);
- /* if body_connection !NULL, then ProcessBody has not
- * found the end of the body yet
- */
- if (request && request->body_reader != NULL) {
- request->body_reader = NULL; // refcounted, triggers abort if needed.
- debugs(32, 3, HERE << "setting body_reader = NULL for request " << request);
- }
+ // Even though freeResources() below may destroy the request,
+ // we no longer set request->body_pipe to NULL here
+ // because we did not initiate that pipe (ConnStateData did)
/* the ICP check here was erroneous
* - storeReleaseRequest was always called if entry was valid
freeResources();
#if ICAP_CLIENT
-
- if (icap)
- delete icap;
-
+ if (icapHeadSource != NULL) {
+ icapHeadSource->noteInitiatorAborted();
+ icapHeadSource = NULL;
+ }
+ if (icapBodySource != NULL)
+ stopConsumingFrom(icapBodySource);
#endif
if (calloutContext)
ClientRequestContext::icapAclCheckDone(ICAPServiceRep::Pointer service)
{
debugs(93,3,HERE << this << " icapAclCheckDone called");
- /*
- * No matching ICAP service in the config file
- */
-
- if (service == NULL) {
- http->doCallouts();
- return;
- }
-
- /*
- * Setup ICAP state and such. If successful, just return.
- * We'll get back to doCallouts() after REQMOD is done.
- */
assert(http);
- if (0 == http->doIcap(service))
+ if (http->startIcap(service))
return;
- /*
- * If doIcap() fails, then we have to either return an error
- * 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.
+ if (!service || service->bypass) {
+ // handle ICAP start failure when no service was selected
+ // or where the selected service was optional
+ http->doCallouts();
+ return;
+ }
- http->doCallouts();
+ // handle start failure for an essential ICAP service
+ http->handleIcapFailure();
}
#endif
;
}
- if (old_request->body_reader != NULL) {
- new_request->body_reader = old_request->body_reader;
- old_request->body_reader = NULL;
- debugs(0,0,HERE << "setting body_reader = NULL for request " << old_request);
+ if (old_request->body_pipe != NULL) {
+ new_request->body_pipe = old_request->body_pipe;
+ old_request->body_pipe = NULL;
+ debugs(0,0,HERE << "redirecting body_pipe " << new_request->body_pipe << " from request " << old_request << " to " << new_request);
}
new_request->content_length = old_request->content_length;
/* FIXME PIPELINE: This is innacurate during pipelining */
- if (http->getConn().getRaw() != NULL)
+ if (http->getConn() != NULL)
fd_note(http->getConn()->fd, http->uri);
assert(http->uri);
#if ICAP_CLIENT
/*
- * Initiate an ICAP transaction. Return 0 if all is well, or -1 upon error.
- * Caller will handle error condition by generating a Squid error message
- * or take other action.
+ * Initiate an ICAP transaction. Return false on errors.
+ * The caller must handle errors.
*/
-int
-ClientHttpRequest::doIcap(ICAPServiceRep::Pointer service)
-{
- debugs(85, 3, HERE << this << " ClientHttpRequest::doIcap() called");
- assert(NULL == icap);
- icap = new ICAPClientReqmodPrecache(service);
- icap->startReqMod(this, request);
-
- if (request->body_reader == NULL) {
- debugs(32, 3, HERE << "client request hasnt body...");
- icap->doneSending();
-
- }
-
- return 0;
-}
-
-/*
- * icapSendRequestBodyWrapper
- *
- * A callback wrapper for ::icapSendRequestBody()
- *
- * icapSendRequestBodyWrapper is of type CBCB
- */
-void
-ClientHttpRequest::icapSendRequestBodyWrapper(MemBuf &mb, void *data)
-{
- ClientHttpRequest *chr = static_cast<ClientHttpRequest*>(data);
- chr->icapSendRequestBody(mb);
-}
-
-
-/*
- * icapSendRequestBody
- *
- * Sends some chunk of a request body to the ICAP side. Must make sure
- * that the ICAP-side can accept the data we have. If there is more
- * body data to read, then schedule another BodyReader callback.
- */
-void
-ClientHttpRequest::icapSendRequestBody(MemBuf &mb)
+bool
+ClientHttpRequest::startIcap(ICAPServiceRep::Pointer service)
{
- ssize_t size_to_send = mb.contentSize();
- debugs(32,3,HERE << "have " << mb.contentSize() << " bytes in mb");
-
- if (size_to_send == 0) {
- /*
- * An error occurred during this transaction. Tell ICAP that we're done.
- */
-
- if (icap)
- icap->doneSending();
-
- return;
- }
-
- debugs(32,3,HERE << "icap->potentialSpaceSize() = " << icap->potentialSpaceSize());
-
- if (size_to_send > icap->potentialSpaceSize())
- size_to_send = icap->potentialSpaceSize();
-
- if (size_to_send) {
- debugs(32,3,HERE << "sending " << size_to_send << " body bytes to ICAP");
- StoreIOBuffer sbuf(size_to_send, 0, mb.content());
- icap->sendMoreData(sbuf);
- icap->body_reader->consume(size_to_send);
- icap->body_reader->bytes_read += size_to_send;
- debugs(32,3," HTTP client body bytes_read=" << icap->body_reader->bytes_read);
- } else {
- 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;
- }
-
- /*
- * If we sent some data this time, and there is more data to
- * read, then schedule another read request via BodyReader.
- */
- if (size_to_send && icap->body_reader->remaining()) {
- debugs(32,3,HERE << "calling body_reader->read()");
- icap->body_reader->read(icapSendRequestBodyWrapper, this);
- } else {
- debugs(32,3,HERE << "No more request body bytes to send");
- icap->doneSending();
+ debugs(85, 3, HERE << this << " ClientHttpRequest::startIcap() called");
+ if (!service) {
+ debug(85,3)("ClientHttpRequest::startIcap fails: lack of service\n");
+ return false;
}
-}
-
-/*
- * Called by ICAPAnchor when it has space available for us.
- */
-void
-ClientHttpRequest::icapSpaceAvailable()
-{
- debugs(85,3,HERE << this << " ClientHttpRequest::icapSpaceAvailable() called\n");
-
- if (request->body_reader != NULL && icap->body_reader == NULL) {
- debugs(32,3,HERE << "reassigning HttpRequest->body_reader to ICAP");
- /*
- * ICAP hooks on to the BodyReader that gets data from
- * ConnStateData. We'll make a new BodyReader that
- * HttpStateData can use if the adapted response has a
- * request body. See ICAPClientReqmodPrecache::noteSourceStart()
- */
- icap->body_reader = request->body_reader;
- request->body_reader = NULL;
+ if (service->broken()) {
+ debug(85,3)("ClientHttpRequest::startIcap fails: broken service\n");
+ return false;
}
- if (icap->body_reader == NULL)
- return;
-
- if (icap->body_reader->callbackPending())
- return;
-
- debugs(32,3,HERE << "Calling read() for body data");
-
- icap->body_reader->read(icapSendRequestBodyWrapper, this);
+ assert(!icapHeadSource);
+ assert(!icapBodySource);
+ icapHeadSource = new ICAPModXact(this, request, NULL, service);
+ ICAPModXact::AsyncStart(icapHeadSource.getRaw());
+ return true;
}
void
-ClientHttpRequest::takeAdaptedHeaders(HttpMsg *msg)
+ClientHttpRequest::noteIcapHeadersAdapted()
{
- debug(85,3)("ClientHttpRequest::takeAdaptedHeaders() called\n");
assert(cbdataReferenceValid(this)); // indicates bug
+ HttpMsg *msg = icapHeadSource->adapted.header;
+ assert(msg);
+
if (HttpRequest *new_req = dynamic_cast<HttpRequest*>(msg)) {
/*
- * Replace the old request with the new request. First,
- * Move the "body_connection" over, then unlink old and
- * link new to the http state.
+ * Replace the old request with the new request.
*/
HTTPMSGUNLOCK(request);
request = HTTPMSGLOCK(new_req);
} else if (HttpReply *new_rep = dynamic_cast<HttpReply*>(msg)) {
debugs(85,3,HERE << "REQMOD reply is HTTP reply");
+ // subscribe to receive reply body
+ if (new_rep->body_pipe != NULL) {
+ icapBodySource = new_rep->body_pipe;
+ assert(icapBodySource->setConsumerIfNotLate(this));
+ }
+
clientStreamNode *node = (clientStreamNode *)client_stream.tail->prev->data;
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
repContext->createStoreEntry(request->method, request->flags);
clientGetMoreData(node, this);
}
+ // we are done with getting headers (but may be receiving body)
+ icapHeadSource = NULL;
+
if (!request_satisfaction_mode)
doCallouts();
-
- debug(85,3)("ClientHttpRequest::takeAdaptedHeaders() finished\n");
}
void
-ClientHttpRequest::takeAdaptedBody(MemBuf *buf)
+ClientHttpRequest::noteIcapHeadersAborted()
{
- debug(85,3)("ClientHttpRequest::takeAdaptedBody() called\n");
-
- if (request_satisfaction_mode) {
- storeEntry()->write(StoreIOBuffer(buf, request_satisfaction_offset));
- request_satisfaction_offset += buf->contentSize();
- buf->consume(buf->contentSize()); // consume everything written
- } else {
- debug(85,0)("Unexpected call to takeAdaptedBody when "
- "not in request_satisfaction_mode");
- }
+ icapHeadSource = NULL;
+ assert(!icapBodySource);
+ handleIcapFailure();
}
void
-ClientHttpRequest::doneAdapting()
+ClientHttpRequest::noteMoreBodyDataAvailable(BodyPipe &)
{
- debug(85,3)("ClientHttpRequest::doneAdapting() called\n");
+ assert(request_satisfaction_mode);
+ assert(icapBodySource != NULL);
+
+ if (const size_t contentSize = icapBodySource->buf().contentSize()) {
+ BodyPipeCheckout bpc(*icapBodySource);
+ const StoreIOBuffer ioBuf(&bpc.buf, request_satisfaction_offset);
+ storeEntry()->write(ioBuf);
+ // assume can write everything
+ request_satisfaction_offset += contentSize;
+ bpc.buf.consume(contentSize);
+ bpc.checkIn();
+ }
+
+ if (icapBodySource->exhausted())
+ endRequestSatisfaction();
+ // else wait for more body data
}
void
-ClientHttpRequest::abortAdapting()
+ClientHttpRequest::noteBodyProductionEnded(BodyPipe &)
{
- debug(85,3)("ClientHttpRequest::abortAdapting() called\n");
-
- if ((NULL == storeEntry()) || storeEntry()->isEmpty()) {
- debug(85,3)("WARNING: ICAP REQMOD callout failed, proceeding with original request\n");
+ assert(!icapHeadSource);
+ if (icapBodySource != NULL) { // did not end request satisfaction yet
+ // We do not expect more because noteMoreBodyDataAvailable always
+ // consumes everything. We do not even have a mechanism to consume
+ // leftovers after noteMoreBodyDataAvailable notifications seize.
+ assert(icapBodySource->exhausted());
+ endRequestSatisfaction();
+ }
+}
- if (calloutContext)
- doCallouts();
+void
+ClientHttpRequest::endRequestSatisfaction() {
+ debugs(85,4, HERE << this << " ends request satisfaction");
+ assert(request_satisfaction_mode);
+ stopConsumingFrom(icapBodySource);
-#if ICAP_HARD_ERROR
+ // TODO: anything else needed to end store entry formation correctly?
+ storeEntry()->complete();
+}
- clientStreamNode *node = (clientStreamNode *)client_stream.tail->prev->data;
+void
+ClientHttpRequest::noteBodyProducerAborted(BodyPipe &)
+{
+ assert(!icapHeadSource);
+ stopConsumingFrom(icapBodySource);
+ handleIcapFailure();
+}
- clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
+void
+ClientHttpRequest::handleIcapFailure()
+{
+ debugs(85,3, HERE << "handleIcapFailure");
- assert (repContext);
+ const bool usedStore = storeEntry() && !storeEntry()->isEmpty();
+ const bool usedPipe = request->body_pipe != NULL &&
+ request->body_pipe->consumedSize() > 0;
- // Note if this code is ever used, clientBuildError() should be modified to
- // accept an errno arg
- repContext->setReplyToError(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR,
- request->method, NULL,
- getConn().getRaw() != NULL ? &getConn()->peer.sin_addr : &no_addr, request,
- NULL, getConn().getRaw() != NULL
- && getConn()->auth_user_request ? getConn()->
- auth_user_request : request->auth_user_request, errno);
+ // XXX: we must not try to recover if the ICAP service is not bypassable!
- node = (clientStreamNode *)client_stream.tail->data;
+ if (!usedStore && !usedPipe) {
+ debug(85,2)("WARNING: ICAP REQMOD callout failed, proceeding with original request\n");
+ if (calloutContext)
+ doCallouts();
+ return;
+ }
- clientStreamRead(node, this, node->readBuffer);
+ debugs(85,3, HERE << "ICAP REQMOD callout failed, responding with error");
-#endif
+ clientStreamNode *node = (clientStreamNode *)client_stream.tail->prev->data;
+ clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
+ assert(repContext);
- return;
- }
+ // The original author of the code also wanted to pass an errno to
+ // setReplyToError, but it seems unlikely that the errno reflects the
+ // true cause of the error at this point, so I did not pass it.
+ ConnStateData::Pointer c = getConn();
+ repContext->setReplyToError(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR,
+ request->method, NULL,
+ (c != NULL ? &c->peer.sin_addr : &no_addr), request, NULL,
+ (c != NULL && c->auth_user_request ?
+ c->auth_user_request : request->auth_user_request));
- debug(0,0)("write me at %s:%d\n", __FILE__,__LINE__);
+ node = (clientStreamNode *)client_stream.tail->data;
+ clientStreamRead(node, this, node->readBuffer);
}
#endif
/*
- * $Id: client_side_request.h,v 1.26 2006/04/27 19:27:37 wessels Exp $
+ * $Id: client_side_request.h,v 1.27 2007/04/06 04:50:06 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
#if ICAP_CLIENT
#include "ICAP/ICAPServiceRep.h"
-
-class ICAPClientReqmodPrecache;
+#include "ICAP/ICAPInitiator.h"
+#include "ICAP/ICAPModXact.h"
class HttpMsg;
#endif
class ClientRequestContext;
class ClientHttpRequest
+#if ICAP_CLIENT
+ : public ICAPInitiator, // to start ICAP transactions
+ public BodyConsumer // to receive reply bodies in request satisf. mode
+#endif
{
public:
#if ICAP_CLIENT
public:
- ICAPClientReqmodPrecache *icap;
- int doIcap(ICAPServiceRep::Pointer);
- void icapSendRequestBody(MemBuf&);
- static void icapSendRequestBodyWrapper(MemBuf&, void*);
- void icapSpaceAvailable();
- void takeAdaptedHeaders(HttpMsg *);
- void takeAdaptedBody(MemBuf *);
- void doneAdapting();
- void abortAdapting();
+ bool startIcap(ICAPServiceRep::Pointer);
+ void handleIcapFailure(); // private but exposed for ClientRequestContext
+
+private:
+ // ICAPInitiator API, called by ICAPXaction
+ virtual void noteIcapHeadersAdapted();
+ virtual void noteIcapHeadersAborted();
+
+ // BodyConsumer API, called by BodyPipe
+ virtual void noteMoreBodyDataAvailable(BodyPipe &);
+ virtual void noteBodyProductionEnded(BodyPipe &);
+ virtual void noteBodyProducerAborted(BodyPipe &);
+
+ void endRequestSatisfaction();
+
+private:
+ ICAPModXact::Pointer icapHeadSource;
+ BodyPipe::Pointer icapBodySource;
+
bool request_satisfaction_mode;
off_t request_satisfaction_offset;
#endif
/*
- * $Id: forward.cc,v 1.153 2007/02/25 11:32:32 hno Exp $
+ * $Id: forward.cc,v 1.154 2007/04/06 04:50:06 rousskov Exp $
*
* DEBUG: section 17 Request Forwarding
* AUTHOR: Duane Wessels
* even if the method is indempotent
*/
- if (request->body_reader != NULL)
+ if (request->body_pipe != NULL)
return false;
/* RFC2616 9.1 Safe and Idempotent Methods */
/*
- * $Id: ftp.cc,v 1.409 2007/01/01 21:40:33 hno Exp $
+ * $Id: ftp.cc,v 1.410 2007/04/06 04:50:06 rousskov Exp $
*
* DEBUG: section 9 File Transfer Protocol (FTP)
* AUTHOR: Harvest Derived
#include "URLScheme.h"
#if ICAP_CLIENT
-#include "ICAP/ICAPClientRespmodPrecache.h"
#include "ICAP/ICAPConfig.h"
+#include "ICAP/ICAPModXact.h"
extern ICAPConfig TheICAPConfig;
static void icapAclCheckDoneWrapper(ICAPServiceRep::Pointer service, void *data);
#endif
bool put_mkdir;
bool listformat_unknown;
bool listing_started;
+ bool completed_forwarding;
};
class FtpStateData;
void listingFinish();
void scheduleReadControlReply(int);
void handleControlReply();
+ void readStor();
char *htmlifyListEntry(const char *line);
void parseListing();
void dataComplete();
void buildTitleUrl();
void writeReplyBody(const char *, int len);
void printfReplyBody(const char *fmt, ...);
- void maybeReadData();
- void transactionComplete();
- void transactionForwardComplete();
- void transactionAbort();
+ virtual int dataDescriptor() const;
+ virtual void maybeReadVirginBody();
+ virtual void closeServer();
+ virtual void completeForwarding();
+ virtual void abortTransaction(const char *reason);
void processReplyBody();
void writeCommand(const char *buf);
static CNCB ftpPasvCallback;
static IOCB dataReadWrapper;
static PF ftpDataWrite;
- static IOCB ftpDataWriteCallback;
static PF ftpTimeout;
static IOCB ftpReadControlReply;
static IOCB ftpWriteCommandCallback;
static HttpReply *ftpAuthRequired(HttpRequest * request, const char *realm);
- static CBCB ftpRequestBody;
static wordlist *ftpParseControlReply(char *, size_t, int *, int *);
-#if ICAP_CLIENT
+ // sending of the request body to the server
+ virtual void sentRequestBody(int fd, size_t size, comm_err_t errflag);
+ virtual void doneSendingRequestBody();
+
+ virtual bool doneWithServer() const;
+private:
+ // BodyConsumer for HTTP: consume request body.
+ virtual void handleRequestBodyProducerAborted();
+
+#if ICAP_CLIENT
public:
void icapAclCheckDone(ICAPServiceRep::Pointer);
- 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
};
safe_free(dirpath);
safe_free(data.host);
- /* XXX this is also set to NULL in transactionForwardComplete */
+
fwd = NULL; // refcounted
}
size_t usable;
StoreEntry *e = entry;
size_t len = data.readBuf->contentSize();
+
+ if (!len) {
+ debug(9, 3) ("ftpParseListing: no content to parse for %s\n", storeUrl(e));
+ return;
+ }
+
/*
* We need a NULL-terminated buffer for scanning, ick
*/
assert(t != NULL);
#if ICAP_CLIENT
-
- if (icap) {
- if ((int)strlen(t) > icap->potentialSpaceSize()) {
+ if (virginBodyDestination != NULL) {
+ // XXX: There are other places where writeReplyBody may overflow!
+ if ((int)strlen(t) > virginBodyDestination->buf().potentialSpaceSize()) {
debugs(0,0,HERE << "WARNING avoid overwhelming ICAP with data!");
usable = s - sbuf;
break;
}
}
-
#endif
writeReplyBody(t, strlen(t));
xfree(sbuf);
}
+int
+FtpStateData::dataDescriptor() const {
+ return data.fd;
+}
+
void
FtpStateData::dataComplete()
{
}
void
-FtpStateData::maybeReadData()
+FtpStateData::maybeReadVirginBody()
{
if (data.fd < 0)
return;
int read_sz = data.readBuf->spaceSize();
#if ICAP_CLIENT
-
- if (icap) {
- int icap_space = icap->potentialSpaceSize();
-
- if (icap_space < read_sz)
- read_sz = icap_space;
- }
-
+ // See HttpStateData::maybeReadVirginBody() for a size-limiting piece of
+ // code that used to be there. Hopefully, it is not really needed.
#endif
debugs(11,9, HERE << "FTP may read up to " << read_sz << " bytes");
#endif
if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
- transactionAbort();
+ abortTransaction("entry aborted during dataRead");
return;
}
if (ignoreErrno(xerrno)) {
commSetTimeout(fd, Config.Timeout.read, ftpTimeout, this);
- maybeReadData();
+ maybeReadVirginBody();
} else {
if (!flags.http_header_sent && !fwd->ftpPasvFailed() && flags.pasv_supported) {
fwd->dontRetry(false); /* this is a retryable error */
storeBufferFlush(entry);
- maybeReadData();
+ maybeReadVirginBody();
}
/*
entry->replaceHttpReply(reply);
- transactionComplete();
+ serverComplete();
return;
}
return;
if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
- ftpState->transactionAbort();
+ ftpState->abortTransaction("entry aborted during control reply read");
return;
}
return;
}
- /* XXX this may end up having to be transactionComplete() .. */
- ftpState->transactionAbort();
+ /* XXX this may end up having to be serverComplete() .. */
+ ftpState->abortTransaction("zero control reply read");
return;
}
*/
if (!EBIT_TEST(ftpState->entry->flags, ENTRY_ABORTED))
- ftpState->transactionForwardComplete();
+ ftpState->completeForwarding();
ftpSendQuit(ftpState);
return;
if (EBIT_TEST(ftpState->entry->flags, ENTRY_ABORTED)) {
- ftpState->transactionAbort();
+ ftpState->abortTransaction("entry aborted when accepting data conn");
return;
}
static void
ftpReadStor(FtpStateData * ftpState)
{
- int code = ftpState->ctrl.replycode;
+ ftpState->readStor();
+}
+
+void FtpStateData::readStor() {
+ int code = ctrl.replycode;
debug(9, 3) ("This is ftpReadStor\n");
- if (code == 125 || (code == 150 && ftpState->data.host)) {
+ if (code == 125 || (code == 150 && data.host)) {
+ // register to receive body data
+ assert(request->body_pipe != NULL);
+ if (!request->body_pipe->setConsumerIfNotLate(this)) {
+ debug(9, 3) ("ftpReadStor: aborting on partially consumed body\n");
+ ftpFail(this);
+ return;
+ }
+
/* Begin data transfer */
debug(9, 3) ("ftpReadStor: starting data transfer\n");
- commSetSelect(ftpState->data.fd,
- COMM_SELECT_WRITE,
- FtpStateData::ftpDataWrite,
- ftpState,
- Config.Timeout.read);
+ sendMoreRequestBody();
/*
* Cancel the timeout on the Control socket and
* establish one on the data socket.
*/
- commSetTimeout(ftpState->ctrl.fd, -1, NULL, NULL);
- commSetTimeout(ftpState->data.fd, Config.Timeout.read, FtpStateData::ftpTimeout,
- ftpState);
- ftpState->state = WRITING_DATA;
+ commSetTimeout(ctrl.fd, -1, NULL, NULL);
+ commSetTimeout(data.fd, Config.Timeout.read, FtpStateData::ftpTimeout,
+ this);
+
+ state = WRITING_DATA;
debug(9, 3) ("ftpReadStor: writing data channel\n");
} else if (code == 150) {
/* Accept data channel */
debug(9, 3) ("ftpReadStor: accepting data channel\n");
- comm_accept(ftpState->data.fd, ftpAcceptDataConnection, ftpState);
+ comm_accept(data.fd, ftpAcceptDataConnection, this);
} else {
debug(9, 3) ("ftpReadStor: Unexpected reply code %03d\n", code);
- ftpFail(ftpState);
+ ftpFail(this);
}
}
if (code == 125 || (code == 150 && ftpState->data.host)) {
/* Begin data transfer */
/* XXX what about Config.Timeout.read? */
- ftpState->maybeReadData();
+ ftpState->maybeReadVirginBody();
ftpState->state = READING_DATA;
/*
* Cancel the timeout on the Control socket and establish one
/* Begin data transfer */
debug(9, 3) ("ftpReadRetr: reading data channel\n");
/* XXX what about Config.Timeout.read? */
- ftpState->maybeReadData();
+ ftpState->maybeReadVirginBody();
ftpState->state = READING_DATA;
/*
* Cancel the timeout on the Control socket and establish one
}
}
-/* This will be called when there is data available to put */
+// premature end of the request body
void
-FtpStateData::ftpRequestBody(MemBuf &mb, void *data)
+FtpStateData::handleRequestBodyProducerAborted()
{
- FtpStateData *ftpState = (FtpStateData *) data;
- debugs(9, 3, HERE << "ftpRequestBody: size=" << mb.contentSize() << " ftpState=%p" << data);
-
- if (mb.contentSize() > 0) {
- /* DataWrite */
- comm_write(ftpState->data.fd, mb.content(), mb.contentSize(), FtpStateData::ftpDataWriteCallback, ftpState, NULL);
- } else if (mb.contentSize() < 0) {
- /* Error */
- debug(9, 1) ("ftpRequestBody: request aborted");
- ftpState->failed(ERR_READ_ERROR, 0);
- } else if (mb.contentSize() == 0) {
- /* End of transfer */
- ftpState->dataComplete();
- }
+ ServerStateData::handleRequestBodyProducerAborted();
+ debugs(9, 3, HERE << "noteBodyProducerAborted: ftpState=" << this);
+ failed(ERR_READ_ERROR, 0);
}
/* This will be called when the put write is completed */
void
-FtpStateData::ftpDataWriteCallback(int fd, char *buf, size_t size, comm_err_t err, int xerrno, void *data)
-{
- FtpStateData *ftpState = (FtpStateData *) data;
-
- if (err == COMM_ERR_CLOSING)
- return;
-
- if (!err) {
- /* Schedule the rest of the request */
- commSetSelect(fd,
- COMM_SELECT_WRITE,
- ftpDataWrite,
- ftpState,
- Config.Timeout.read);
- } else {
- debug(9, 1) ("ftpDataWriteCallback: write error: %s\n", xstrerr(xerrno));
- ftpState->failed(ERR_WRITE_ERROR, xerrno);
- }
-}
-
-void
-FtpStateData::ftpDataWrite(int ftp, void *data)
+FtpStateData::sentRequestBody(int fd, size_t size, comm_err_t errflag)
{
- FtpStateData *ftpState = (FtpStateData *) data;
- debug(9, 3) ("ftpDataWrite\n");
- /* This starts the body transfer */
- ftpState->request->body_reader->read(ftpRequestBody, ftpState);
+ if (size > 0)
+ kb_incr(&statCounter.server.ftp.kbytes_out, size);
+ ServerStateData::sentRequestBody(fd, size, errflag);
}
static void
static void
ftpReadQuit(FtpStateData * ftpState)
{
- /* XXX should this just be a case of transactionAbort? */
- ftpState->transactionComplete();
+ /* XXX should this just be a case of abortTransaction? */
+ ftpState->serverComplete();
}
static void
if (entry->isEmpty())
failedErrorMessage(error, xerrno);
- transactionComplete();
+ serverComplete();
}
void
FtpStateData::writeReplyBody(const char *data, int len)
{
#if ICAP_CLIENT
-
- if (icap) {
+ if (virginBodyDestination != NULL) {
debugs(9,5,HERE << "writing " << len << " bytes to ICAP");
- icap->sendMoreData (StoreIOBuffer(len, 0, (char*)data));
+ const size_t putSize = virginBodyDestination->putMoreData(data, len);
+ if (putSize != (size_t)len) {
+ // XXX: FTP writing should be rewritten to avoid temporary buffers
+ // because temporary buffers cannot handle overflows.
+ debugs(0,0,HERE << "ICAP cannot keep up with FTP; lost " <<
+ (len - putSize) << '/' << len << " bytes.");
+ }
return;
}
-
#endif
debugs(9,5,HERE << "writing " << len << " bytes to StoreEntry");
storeAppend(entry, data, len);
}
-/*
- * We've completed with the forwardstate - finish up if necessary.
- * This is a simple hack to ensure we don't double-complete on the
- * forward entry.
- */
+// called after we wrote the last byte of the request body
void
-FtpStateData::transactionForwardComplete()
+FtpStateData::doneSendingRequestBody()
{
- debugs(9,5,HERE << "transactionForwardComplete FD " << ctrl.fd << ", Data FD " << data.fd << ", this " << this);
- if (fwd == NULL) {
- fwd->complete();
- /* XXX this is also set to NULL in the destructor, but we need to do it as early as possible.. -adrian */
- fwd = NULL; // refcounted
- }
-
+ debugs(9,3,HERE << "doneSendingRequestBody");
+ ftpWriteTransferDone(this);
}
-/*
- * Quickly abort a connection.
- * This will, for now, just call comm_close(). That'll unravel everything
- * properly (I hope!) by using abort handlers. This all has to change soon
- * enough!
- */
+// a hack to ensure we do not double-complete on the forward entry.
+// TODO: FtpStateData logic should probably be rewritten to avoid
+// double-completion or FwdState should be rewritten to allow it.
void
-FtpStateData::transactionAbort()
+FtpStateData::completeForwarding()
{
- debugs(9,5,HERE << "transactionAbort FD " << ctrl.fd << ", Data FD " << data.fd << ", this " << this);
- assert(ctrl.fd != -1);
+ if (fwd == NULL || flags.completed_forwarding) {
+ debugs(9,2,HERE << "completeForwarding avoids " <<
+ "double-complete on FD " << ctrl.fd << ", Data FD " << data.fd <<
+ ", this " << this << ", fwd " << fwd);
+ return;
+ }
- comm_close(ctrl.fd);
- /* We could have had our state data freed from underneath us here.. */
+ flags.completed_forwarding = true;
+ ServerStateData::completeForwarding();
}
-/*
- * Done with the FTP server, so close those sockets. May not be
- * done with ICAP yet though. Don't free ftpStateData if ICAP is
- * still around.
- */
+// Close the FTP server connection(s). Used by serverComplete().
void
-FtpStateData::transactionComplete()
+FtpStateData::closeServer()
{
- debugs(9,5,HERE << "transactionComplete FD " << ctrl.fd << ", Data FD " << data.fd << ", this " << this);
+ debugs(9,5, HERE << "closing FTP server FD " << ctrl.fd << ", Data FD " << data.fd << ", this " << this);
if (ctrl.fd > -1) {
fwd->unregister(ctrl.fd);
comm_close(data.fd);
data.fd = -1;
}
+}
-#if ICAP_CLIENT
-
- if (icap) {
- icap->doneSending();
- return;
- }
-
-#endif
-
- transactionForwardComplete();
+// Did we close all FTP server connection(s)?
+bool
+FtpStateData::doneWithServer() const
+{
+ return ctrl.fd < 0 && data.fd < 0;
+}
- ftpSocketClosed(-1, this);
+// Quickly abort the transaction
+// TODO: destruction should be sufficient as the destructor should cleanup,
+// including canceling close handlers
+void
+FtpStateData::abortTransaction(const char *reason)
+{
+ debugs(9,5,HERE << "aborting transaction for " << reason <<
+ "; FD " << ctrl.fd << ", Data FD " << data.fd << ", this " << this);
+ if (ctrl.fd >= 0)
+ comm_close(ctrl.fd);
+ else
+ delete this;
}
#if ICAP_CLIENT
ftpState->icapAclCheckDone(service);
}
+// TODO: merge with http.cc and move to Server.cc?
void
FtpStateData::icapAclCheckDone(ICAPServiceRep::Pointer service)
{
icapAccessCheckPending = false;
- const bool startedIcap = startIcap(service);
+ const bool startedIcap = startIcap(service, request);
if (!startedIcap && (!service || service->bypass)) {
// handle ICAP start failure when no service was selected
return;
}
- icap->startRespMod(this, request, reply);
processReplyBody();
}
-/*
- * Called by ICAPClientRespmodPrecache when it has space available for us.
- */
-void
-FtpStateData::icapSpaceAvailable()
-{
- debug(11,5)("FtpStateData::icapSpaceAvailable() called\n");
- maybeReadData();
-}
-
-bool
-FtpStateData::takeAdaptedHeaders(HttpReply *rep)
-{
- debug(11,5)("FtpStateData::takeAdaptedHeaders() called\n");
-
- if (!entry->isAccepting()) {
- debug(11,5)("\toops, entry is not Accepting!\n");
- backstabAdapter();
- return false;
- }
-
- assert (rep);
- entry->replaceHttpReply(rep);
- HTTPMSGUNLOCK(reply);
-
- reply = HTTPMSGLOCK(rep);
-
- debug(11,5)("FtpStateData::takeAdaptedHeaders() finished\n");
- return true;
-}
-
-bool
-FtpStateData::takeAdaptedBody(MemBuf *buf)
-{
- debug(11,5)("FtpStateData::takeAdaptedBody() called\n");
- debug(11,5)("\t%d bytes\n", (int) buf->contentSize());
-
- if (!entry->isAccepting()) {
- debug(11,5)("\toops, entry is not Accepting!\n");
- backstabAdapter();
- return false;
- }
-
- storeAppend(entry, buf->content(), buf->contentSize());
- buf->consume(buf->contentSize()); // consume everything written
- return true;
-}
-
-void
-FtpStateData::finishAdapting()
-{
- debug(11,5)("FtpStateData::doneAdapting() called\n");
-
- if (!entry->isAccepting()) {
- debug(11,5)("\toops, entry is not Accepting!\n");
- backstabAdapter();
- } else {
- transactionForwardComplete();
- endAdapting();
- }
-}
-
-void
-FtpStateData::abortAdapting()
-{
- debug(11,5)("FtpStateData::abortAdapting() called\n");
-
- if (entry->isEmpty()) {
- ErrorState *err;
- err = errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
- err->xerrno = errno;
- fwd->fail(err);
- 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.510 2007/02/09 13:29:05 hno Exp $
+ * $Id: http.cc,v 1.511 2007/04/06 04:50:06 rousskov Exp $
*
* DEBUG: section 11 Hypertext Transfer Protocol (HTTP)
* AUTHOR: Harvest Derived
#include "DelayPools.h"
#endif
#if ICAP_CLIENT
-#include "ICAP/ICAPClientRespmodPrecache.h"
#include "ICAP/ICAPConfig.h"
extern ICAPConfig TheICAPConfig;
#endif
static void icapAclCheckDoneWrapper(ICAPServiceRep::Pointer service, void *data);
#endif
-HttpStateData::HttpStateData(FwdState *theFwdState) : ServerStateData(theFwdState)
+HttpStateData::HttpStateData(FwdState *theFwdState) : ServerStateData(theFwdState),
+ header_bytes_read(0), reply_bytes_read(0)
{
debugs(11,5,HERE << "HttpStateData " << this << " created");
ignoreCacheControl = false;
* don't forget that ~ServerStateData() gets called automatically
*/
- if (orig_request->body_reader != NULL) {
- orig_request->body_reader = NULL;
- debugs(32,3,HERE << "setting body_reader = NULL for request " << orig_request);
- }
-
if (!readBuf->isNull())
readBuf->clean();
HTTPMSGUNLOCK(orig_request);
- debugs(11,5,HERE << "HttpStateData " << this << " destroyed");
+ debugs(11,5, HERE << "HttpStateData " << this << " destroyed; FD " << fd);
+}
+
+int
+HttpStateData::dataDescriptor() const {
+ return fd;
}
static void
{
HttpStateData *httpState = static_cast<HttpStateData *>(data);
debug(11,5)("httpStateFree: FD %d, httpState=%p\n", fd, data);
-
- if (httpState)
- delete httpState;
+ delete httpState;
}
int
* condition
*/
#define REFRESH_OVERRIDE(flag) \
- ((R = (R ? R : refreshLimits(entry->mem_obj->url))) , \
- (R && R->flags.flag))
+ ((R = (R ? R : refreshLimits(entry->mem_obj->url))) , \
+ (R && R->flags.flag))
#else
#define REFRESH_OVERRIDE(flag) 0
#endif
*/
if (!refreshIsCachable(entry)) {
- debug(22, 3) ("refreshIsCachable() returned non-cacheable..\n");
+ debug(22, 3) ("refreshIsCachable() returned non-cacheable..\n");
return 0;
- }
+ }
/* don't cache objects from peers w/o LMT, Date, or Expires */
/* check that is it enough to check headers @?@ */
entry->replaceHttpReply(reply);
if (eof == 1) {
- transactionComplete();
+ serverComplete();
}
}
debug(11, 9) ("GOT HTTP REPLY HDR:\n---------\n%s\n----------\n",
readBuf->content());
- readBuf->consume(headersEnd(readBuf->content(), readBuf->contentSize()));
+ header_bytes_read = headersEnd(readBuf->content(), readBuf->contentSize());
+ readBuf->consume(header_bytes_read);
flags.headers_parsed = 1;
haveParsedReplyHeaders();
if (eof == 1) {
- transactionComplete();
+ serverComplete();
}
ctx_exit(ctx);
}
-/*
- * This function used to be joined with processReplyHeader(), but
- * we split it for ICAP.
- */
+// Called when we parsed (and possibly adapted) the headers but
+// had not starting storing (a.k.a., sending) the body yet.
void
HttpStateData::haveParsedReplyHeaders()
{
EBIT_SET(entry->flags, ENTRY_REVALIDATE);
}
- ctx_exit(ctx);
#if HEADERS_LOG
-
headersLog(1, 0, request->method, getReply());
#endif
+
+ ctx_exit(ctx);
}
HttpStateData::ConnectionStatus
HttpStateData::ConnectionStatus
HttpStateData::persistentConnStatus() const
{
- int clen;
debug(11, 3) ("persistentConnStatus: FD %d\n", fd);
- ConnectionStatus result = statusIfComplete();
debug(11, 5) ("persistentConnStatus: content_length=%d\n",
reply->content_length);
- /* If we haven't seen the end of reply headers, we are not done */
+ /* If we haven't seen the end of reply headers, we are not done */
debug(11,5)("persistentConnStatus: flags.headers_parsed=%d\n", flags.headers_parsed);
-
if (!flags.headers_parsed)
return INCOMPLETE_MSG;
- clen = reply->bodySize(request->method);
-
+ const int clen = reply->bodySize(request->method);
debug(11,5)("persistentConnStatus: clen=%d\n", clen);
- /* If there is no message body, we can be persistent */
- if (0 == clen)
- return result;
-
/* If the body size is unknown we must wait for EOF */
if (clen < 0)
return INCOMPLETE_MSG;
- /* If the body size is known, we must wait until we've gotten all of it. */
- /* old technique:
- * if (entry->mem_obj->endOffset() < reply->content_length + reply->hdr_sz) */
- debug(11,5)("persistentConnStatus: body_bytes_read=%d, content_length=%d\n",
- body_bytes_read, reply->content_length);
+ /* If the body size is known, we must wait until we've gotten all of it. */
+ if (clen > 0) {
+ // old technique:
+ // if (entry->mem_obj->endOffset() < reply->content_length + reply->hdr_sz)
+ const int body_bytes_read = reply_bytes_read - header_bytes_read;
+ debugs(11,5, "persistentConnStatus: body_bytes_read=" <<
+ body_bytes_read << " content_length=" << reply->content_length);
- if (body_bytes_read < reply->content_length)
- return INCOMPLETE_MSG;
+ if (body_bytes_read < reply->content_length)
+ return INCOMPLETE_MSG;
+ }
- /* We got it all */
- return result;
+ /* If there is no message body or we got it all, we can be persistent */
+ return statusIfComplete();
}
/*
}
if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
- maybeReadData();
+ maybeReadVirginBody();
return;
}
if (flag == COMM_OK && len > 0) {
readBuf->appended(len);
+ reply_bytes_read += len;
#if DELAY_POOLS
DelayId delayId = entry->mem_obj->mostBytesAllowed();
/* Continue to read... */
/* Timeout NOT increased. This whitespace was from previous reply */
flags.do_next_read = 1;
- maybeReadData();
+ maybeReadVirginBody();
return;
}
}
* definately at EOF, so we want to process the reply
* headers.
*/
- PROF_start(HttpStateData_processReplyHeader);
+ PROF_start(HttpStateData_processReplyHeader);
processReplyHeader();
- PROF_stop(HttpStateData_processReplyHeader);
- }
+ PROF_stop(HttpStateData_processReplyHeader);
+ }
else if (getReply()->sline.status == HTTP_INVALID_HEADER && HttpVersion(0,9) != getReply()->sline.version) {
fwd->fail(errorCon(ERR_INVALID_RESP, HTTP_BAD_GATEWAY, fwd->request));
flags.do_next_read = 0;
flags.do_next_read = 0;
comm_close(fd);
} else {
- transactionComplete();
+ serverComplete();
}
}
} else {
if (!flags.headers_parsed) {
- PROF_start(HttpStateData_processReplyHeader);
+ PROF_start(HttpStateData_processReplyHeader);
processReplyHeader();
- PROF_stop(HttpStateData_processReplyHeader);
+ PROF_stop(HttpStateData_processReplyHeader);
if (flags.headers_parsed) {
bool fail = reply == NULL;
* which should be sent to either StoreEntry, or to ICAP...
*/
void
-HttpStateData::writeReplyBody(const char *data, int len)
+HttpStateData::writeReplyBody()
{
-#if ICAP_CLIENT
+ const char *data = readBuf->content();
+ int len = readBuf->contentSize();
- if (icap) {
- icap->sendMoreData (StoreIOBuffer(len, 0, (char*)data));
+#if ICAP_CLIENT
+ if (virginBodyDestination != NULL) {
+ const size_t putSize = virginBodyDestination->putMoreData(data, len);
+ readBuf->consume(putSize);
+ return;
+ }
+ // Even if we are done with sending the virgin body to ICAP, we may still
+ // be waiting for adapted headers. We need them before writing to store.
+ if (adaptedHeadSource != NULL) {
+ debugs(11,5, HERE << "need adapted head from " << adaptedHeadSource);
return;
}
-
#endif
entry->write (StoreIOBuffer(len, currentOffset, (char*)data));
-
+ readBuf->consume(len);
currentOffset += len;
}
{
if (!flags.headers_parsed) {
flags.do_next_read = 1;
- maybeReadData();
+ maybeReadVirginBody();
return;
}
#if ICAP_CLIENT
if (icapAccessCheckPending)
return;
-
#endif
/*
* That means header content has been removed from readBuf and
* it contains only body data.
*/
- writeReplyBody(readBuf->content(), readBuf->contentSize());
-
- body_bytes_read += readBuf->contentSize();
-
- readBuf->consume(readBuf->contentSize());
+ writeReplyBody();
if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
/*
fd = -1;
- transactionComplete();
+ serverComplete();
return;
case COMPLETE_NONPERSISTENT_MSG:
debug(11,5)("processReplyBody: COMPLETE_NONPERSISTENT_MSG\n");
- transactionComplete();
+ serverComplete();
return;
}
- maybeReadData();
+ maybeReadVirginBody();
}
void
-HttpStateData::maybeReadData()
+HttpStateData::maybeReadVirginBody()
{
int read_sz = readBuf->spaceSize();
-#if ICAP_CLIENT
- if (icap) {
+#if ICAP_CLIENT
+#if RE_ENABLE_THIS_IF_NEEDED_OR_DELETE
+ // This code is not broken, but is probably not needed because we
+ // probably can read more than will fit into the BodyPipe buffer.
+ if (virginBodyDestination != NULL) {
/*
- * Our ICAP message pipes have a finite size limit. We
+ * BodyPipe buffer has a finite size limit. We
* should not read more data from the network than will fit
* into the pipe buffer. If totally full, don't register
* the read handler at all. The ICAP side will call our
* icapSpaceAvailable() method when it has free space again.
*/
- int icap_space = icap->potentialSpaceSize();
+ int icap_space = virginBodyDestination->buf().potentialSpaceSize();
debugs(11,9, "HttpStateData may read up to min(" << icap_space <<
", " << read_sz << ") bytes");
if (icap_space < read_sz)
read_sz = icap_space;
}
-
+#endif
#endif
- debugs(11,9, "HttpStateData may read up to " << read_sz << " bytes");
+ debugs(11,9, HERE << (flags.do_next_read ? "may" : "wont") <<
+ " read up to " << read_sz << " bytes from FD " << fd);
/*
* why <2? Because delayAwareRead() won't actually read if
httpState->flags.request_sent = 1;
}
-/*
- * Calling this function marks the end of the HTTP transaction.
- * i.e., done talking to the HTTP server. With ICAP, however, that
- * does not mean that we're done with HttpStateData and the StoreEntry.
- * We'll be expecting adapted data to come back from the ICAP
- * routines.
- */
+// Close the HTTP server connection. Used by serverComplete().
void
-HttpStateData::transactionComplete()
+HttpStateData::closeServer()
{
- debugs(11,5,HERE << "transactionComplete FD " << fd << " this " << this);
-
+ debugs(11,5, HERE << "closing HTTP server FD " << fd << " this " << this);
if (fd >= 0) {
fwd->unregister(fd);
comm_remove_close_handler(fd, httpStateFree, this);
comm_close(fd);
fd = -1;
}
+}
-#if ICAP_CLIENT
- if (icap) {
- icap->doneSending();
- return;
- }
-
-#endif
-
- fwd->complete();
-
- httpStateFree(-1, this);
+bool
+HttpStateData::doneWithServer() const
+{
+ return fd < 0;
}
/*
}
/* This will be called when connect completes. Write request. */
-void
+bool
HttpStateData::sendRequest()
{
MemBuf mb;
- IOCB *sendHeaderDone;
- debug(11, 5) ("httpSendRequest: FD %d: this %p.\n", fd, this);
+ debug(11, 5) ("httpSendRequest: FD %d, request %p, this %p.\n", fd, request, this);
commSetTimeout(fd, Config.Timeout.lifetime, httpTimeout, this);
flags.do_next_read = 1;
- maybeReadData();
-
- debugs(32,3,HERE<< "request " << request << " body_reader = " << orig_request->body_reader.getRaw());
-
- if (orig_request->body_reader != NULL)
- sendHeaderDone = HttpStateData::SendRequestEntityWrapper;
- else
- sendHeaderDone = HttpStateData::SendComplete;
+ maybeReadVirginBody();
+
+ if (orig_request->body_pipe != NULL) {
+ requestBodySource = orig_request->body_pipe;
+ if (!requestBodySource->setConsumerIfNotLate(this)) {
+ debugs(32,3, HERE << "aborting on partially consumed body");
+ requestBodySource = NULL;
+ return false;
+ }
+ requestSender = HttpStateData::sentRequestBodyWrapper;
+ debugs(32,3, HERE << "expecting request body on pipe " << requestBodySource);
+ } else {
+ assert(!requestBodySource);
+ requestSender = HttpStateData::SendComplete;
+ }
if (_peer != NULL) {
if (_peer->options.originserver) {
mb.init();
buildRequestPrefix(request, orig_request, entry, &mb, flags);
debug(11, 6) ("httpSendRequest: FD %d:\n%s\n", fd, mb.buf);
- comm_write_mbuf(fd, &mb, sendHeaderDone, this);
+ comm_write_mbuf(fd, &mb, requestSender, this);
+
+ return true;
}
void
storeUrl(fwd->entry));
HttpStateData *httpState = new HttpStateData(fwd);
- statCounter.server.all.requests++;
+ if (!httpState->sendRequest()) {
+ debug(11, 3) ("httpStart: aborted");
+ delete httpState;
+ return;
+ }
+ statCounter.server.all.requests++;
statCounter.server.http.requests++;
- httpState->sendRequest();
-
/*
* We used to set the read timeout here, but not any more.
* Now its set in httpSendComplete() after the full request,
}
void
-HttpStateData::sendRequestEntityDone()
+HttpStateData::doneSendingRequestBody()
{
ACLChecklist ch;
- debug(11, 5) ("httpSendRequestEntityDone: FD %d\n", fd);
+ debugs(11,5, HERE << "doneSendingRequestBody: FD " << fd);
ch.request = HTTPMSGLOCK(request);
if (Config.accessList.brokenPosts)
/* cbdataReferenceDone() happens in either fastCheck() or ~ACLCheckList */
if (!Config.accessList.brokenPosts) {
- debug(11, 5) ("httpSendRequestEntityDone: No brokenPosts list\n");
+ debug(11, 5) ("doneSendingRequestBody: No brokenPosts list\n");
HttpStateData::SendComplete(fd, NULL, 0, COMM_OK, 0, this);
} else if (!ch.fastCheck()) {
- debug(11, 5) ("httpSendRequestEntityDone: didn't match brokenPosts\n");
+ debug(11, 5) ("doneSendingRequestBody: didn't match brokenPosts\n");
HttpStateData::SendComplete(fd, NULL, 0, COMM_OK, 0, this);
} else {
- debug(11, 2) ("httpSendRequestEntityDone: matched brokenPosts\n");
+ debug(11, 2) ("doneSendingRequestBody: matched brokenPosts\n");
comm_write(fd, "\r\n", 2, HttpStateData::SendComplete, this, NULL);
}
}
-/*
- * RequestBodyHandlerWrapper
- *
- * BodyReader calls this when it has some body data for us.
- * It is of type CBCB.
- */
+// more origin request body data is available
void
-HttpStateData::RequestBodyHandlerWrapper(MemBuf &mb, void *data)
-{
- HttpStateData *httpState = static_cast<HttpStateData *>(data);
- httpState->requestBodyHandler(mb);
-}
-
-void
-HttpStateData::requestBodyHandler(MemBuf &mb)
+HttpStateData::handleMoreRequestBodyAvailable()
{
if (eof || fd < 0) {
+ // XXX: we should check this condition in other callbacks then!
+ // TODO: Check whether this can actually happen: We should unsubscribe
+ // as a body consumer when the above condition(s) are detected.
debugs(11, 1, HERE << "Transaction aborted while reading HTTP body");
return;
}
- if (mb.contentSize() > 0) {
+ assert(requestBodySource != NULL);
+ if (requestBodySource->buf().hasContent()) {
+ // XXX: why does not this trigger a debug message on every request?
if (flags.headers_parsed && !flags.abuse_detected) {
flags.abuse_detected = 1;
- debug(11, 1) ("httpSendRequestEntryDone: Likely proxy abuse detected '%s' -> '%s'\n",
+ debug(11, 1) ("http handleMoreRequestBodyAvailable: Likely proxy abuse detected '%s' -> '%s'\n",
inet_ntoa(orig_request->client_addr),
storeUrl(entry));
return;
}
}
-
- /*
- * mb's content will be consumed in the SendRequestEntityWrapper
- * callback after comm_write is done.
- */
- flags.consume_body_data = 1;
-
- comm_write(fd, mb.content(), mb.contentSize(), SendRequestEntityWrapper, this, NULL);
- } else if (orig_request->body_reader == NULL) {
- /* Failed to get whole body, probably aborted */
- SendComplete(fd, NULL, 0, COMM_ERR_CLOSING, 0, this);
- } else if (orig_request->body_reader->remaining() == 0) {
- /* End of body */
- sendRequestEntityDone();
- } else {
- /* Failed to get whole body, probably aborted */
- SendComplete(fd, NULL, 0, COMM_ERR_CLOSING, 0, this);
}
+
+ HttpStateData::handleMoreRequestBodyAvailable();
}
+// premature end of the request body
void
-HttpStateData::SendRequestEntityWrapper(int fd, char *bufnotused, size_t size, comm_err_t errflag, int xerrno, void *data)
+HttpStateData::handleRequestBodyProducerAborted()
{
- HttpStateData *httpState = static_cast<HttpStateData *>(data);
- httpState->sendRequestEntity(fd, size, errflag);
+ ServerStateData::handleRequestBodyProducerAborted();
+ // XXX: SendComplete(COMM_ERR_CLOSING) does little. Is it enough?
+ SendComplete(fd, NULL, 0, COMM_ERR_CLOSING, 0, this);
}
+// called when we wrote request headers(!) or a part of the body
void
-HttpStateData::sendRequestEntity(int fd, size_t size, comm_err_t errflag)
+HttpStateData::sentRequestBody(int fd, size_t size, comm_err_t errflag)
{
- debug(11, 5) ("httpSendRequestEntity: FD %d: size %d: errflag %d.\n",
- fd, (int) size, errflag);
- debugs(32,3,HERE << "httpSendRequestEntity called");
-
- /*
- * This used to be an assertion for body_reader != NULL.
- * Currently there are cases where body_reader may become NULL
- * before reaching this point in the code. This can happen
- * because body_reader is attached to HttpRequest and other
- * modules (client_side, ICAP) have access to HttpRequest->body
- * reader. An aborted transaction may cause body_reader to
- * become NULL between the time sendRequestEntity was registered
- * and actually called. For now we'll abort the whole transaction,
- * but this should be fixed so that the client/icap/server sides
- * are cleaned up independently.
- */
-
- if (orig_request->body_reader == NULL) {
- debugs(32,1,HERE << "sendRequestEntity body_reader became NULL, aborting transaction");
- comm_close(fd);
- return;
- }
-
- if (size > 0) {
- fd_bytes(fd, size, FD_WRITE);
- kb_incr(&statCounter.server.all.kbytes_out, size);
+ if (size > 0)
kb_incr(&statCounter.server.http.kbytes_out, size);
+ ServerStateData::sentRequestBody(fd, size, errflag);
+}
- if (flags.consume_body_data) {
- orig_request->body_reader->consume(size);
- orig_request->body_reader->bytes_read += size;
- debugs(32,3," HTTP server body bytes_read=" << orig_request->body_reader->bytes_read);
- }
- }
-
- if (errflag == COMM_ERR_CLOSING)
- return;
-
- if (errflag) {
- ErrorState *err;
- err = errorCon(ERR_WRITE_ERROR, HTTP_BAD_GATEWAY, fwd->request);
- err->xerrno = errno;
- fwd->fail(err);
- comm_close(fd);
- return;
- }
-
- if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+// Quickly abort the transaction
+// TODO: destruction should be sufficient as the destructor should cleanup,
+// including canceling close handlers
+void
+HttpStateData::abortTransaction(const char *reason)
+{
+ debugs(11,5, HERE << "aborting transaction for " << reason <<
+ "; FD " << fd << ", this " << this);
+ if (fd >= 0)
comm_close(fd);
- return;
- }
-
- size_t r = orig_request->body_reader->remaining();
- debugs(32,3,HERE << "body remaining = " << r);
-
- if (r) {
- debugs(32,3,HERE << "reading more body data");
- orig_request->body_reader->read(RequestBodyHandlerWrapper, this);
- } else {
- debugs(32,3,HERE << "done reading body data");
- sendRequestEntityDone();
- }
+ else
+ delete this;
}
void
{
icapAccessCheckPending = false;
- const bool startedIcap = startIcap(service);
+ const bool startedIcap = startIcap(service, orig_request);
if (!startedIcap && (!service || service->bypass)) {
// handle ICAP start failure when no service was selected
processReplyBody();
if (eof == 1)
- transactionComplete();
+ serverComplete();
return;
}
return;
}
- icap->startRespMod(this, orig_request, reply);
processReplyBody();
}
-/*
- * Called by ICAPClientRespmodPrecache when it has space available for us.
- */
-void
-HttpStateData::icapSpaceAvailable()
-{
- debug(11,5)("HttpStateData::icapSpaceAvailable() called\n");
- maybeReadData();
-}
-
-bool
-HttpStateData::takeAdaptedHeaders(HttpReply *rep)
-{
- debug(11,5)("HttpStateData::takeAdaptedHeaders() called\n");
-
- if (!entry->isAccepting()) {
- debug(11,5)("\toops, entry is not Accepting!\n");
- backstabAdapter();
- return false;
- }
-
- assert (rep);
- entry->replaceHttpReply(rep);
- HTTPMSGUNLOCK(reply);
-
- reply = HTTPMSGLOCK(rep);
-
- haveParsedReplyHeaders();
-
- debug(11,5)("HttpStateData::takeAdaptedHeaders() finished\n");
- return true;
-}
-
-bool
-HttpStateData::takeAdaptedBody(MemBuf *buf)
-{
- debug(11,5)("HttpStateData::takeAdaptedBody() called\n");
- debug(11,5)("\t%d bytes\n", (int) buf->contentSize());
- debug(11,5)("\t%d is current offset\n", (int)currentOffset);
-
- if (!entry->isAccepting()) {
- debug(11,5)("\toops, entry is not Accepting!\n");
- 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::finishAdapting()
-{
- debug(11,5)("HttpStateData::finishAdapting() called by %p\n", icap);
-
- if (!entry->isAccepting()) { // XXX: do we need this check here?
- debug(11,5)("\toops, entry is not Accepting!\n");
- backstabAdapter();
- } else {
- fwd->complete();
- endAdapting();
- }
-}
-
-// 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 by %p\n", icap);
-
- if (entry->isEmpty()) {
- ErrorState *err;
- err = errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
- err->xerrno = errno;
- fwd->fail( err);
- fwd->dontRetry(true);
- flags.do_next_read = 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(fd, this); // deletes us
-}
-
#endif
/*
- * $Id: http.h,v 1.26 2006/10/31 23:30:57 wessels Exp $
+ * $Id: http.h,v 1.27 2007/04/06 04:50:06 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
#include "comm.h"
#include "forward.h"
#include "Server.h"
-#include "BodyReader.h"
#if ICAP_CLIENT
#include "ICAP/ICAPServiceRep.h"
-class ICAPClientRespmodPrecache;
-
class ICAPAccessCheck;
#endif
~HttpStateData();
static IOCB SendComplete;
- static IOCB SendRequestEntityWrapper;
static IOCB ReadReplyWrapper;
- static CBCB RequestBodyHandlerWrapper;
static void httpBuildRequestHeader(HttpRequest * request,
HttpRequest * orig_request,
StoreEntry * entry,
HttpHeader * hdr_out,
http_state_flags flags);
+
+ virtual int dataDescriptor() const;
/* should be private */
- void sendRequest();
+ bool sendRequest();
void processReplyHeader();
void processReplyBody();
void readReply(size_t len, comm_err_t flag, int xerrno);
- void maybeReadData();
+ virtual void maybeReadVirginBody(); // read response data from the network
int cacheableReply();
-#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 */
int eof; /* reached end-of-object? */
HttpRequest *orig_request;
http_state_flags flags;
off_t currentOffset;
size_t read_sz;
- int body_bytes_read; /* to find end of response, independent of StoreEntry */
+ int header_bytes_read; // to find end of response,
+ int reply_bytes_read; // without relying on StoreEntry
MemBuf *readBuf;
bool ignoreCacheControl;
bool surrogateNoStore;
+
void processSurrogateControl(HttpReply *);
-#if ICAP_CLIENT
+#if ICAP_CLIENT
void icapAclCheckDone(ICAPServiceRep::Pointer);
bool icapAccessCheckPending;
#endif
void failReply (HttpReply *reply, http_status const &status);
void keepaliveAccounting(HttpReply *);
void checkDateSkew(HttpReply *);
- void haveParsedReplyHeaders();
- void transactionComplete();
- void writeReplyBody(const char *data, int len);
- void sendRequestEntityDone();
+
+ virtual void haveParsedReplyHeaders();
+ virtual void closeServer(); // end communication with the server
+ virtual bool doneWithServer() const; // did we end communication?
+ virtual void abortTransaction(const char *reason); // abnormal termination
+
+ // consuming request body
+ virtual void handleMoreRequestBodyAvailable();
+ virtual void handleRequestBodyProducerAborted();
+
+ void writeReplyBody();
+ void doneSendingRequestBody();
void requestBodyHandler(MemBuf &);
- void sendRequestEntity(int fd, size_t size, comm_err_t errflag);
+ virtual void sentRequestBody(int fd, size_t size, comm_err_t errflag);
mb_size_t buildRequestPrefix(HttpRequest * request,
HttpRequest * orig_request,
StoreEntry * entry,
static bool decideIfWeDoRanges (HttpRequest * orig_request);
#if ICAP_CLIENT
- void backstabAdapter();
- void endAdapting();
#endif
private:
/*
- * $Id: structs.h,v 1.551 2007/02/25 11:32:33 hno Exp $
+ * $Id: structs.h,v 1.552 2007/04/06 04:50:06 rousskov Exp $
*
*
* SQUID Web Proxy Cache http://www.squid-cache.org/
unsigned int must_keepalive:
1;
+
+ // When adding new flags, please update cloneAdaptationImmune() as needed.
+
bool resetTCP() const;
void setResetTCP();
void clearResetTCP();
void destinationIPLookupCompleted();
bool destinationIPLookedUp() const;
+ // returns a partial copy of the flags that includes only those flags
+ // that are safe for a related (e.g., ICAP-adapted) request to inherit
+ request_flags cloneAdaptationImmune() const;
+
private:
unsigned int reset_tcp: