From: rousskov <> Date: Fri, 6 Apr 2007 10:50:04 +0000 (+0000) Subject: - ICAP-unrelated improvements from the squid3-icap branch on SF X-Git-Tag: SQUID_3_0_PRE6~132 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5f8252d203092b380f73e997be7097282c793077;p=thirdparty%2Fsquid.git - ICAP-unrelated improvements from the squid3-icap branch on SF (see further below for ICAP-specific improvements): - Replaced BodyReader with BodyPipe. BodyReader was a collection of function pointers augmented with body size calculation logic. BodyReader was used to deliver request body (of a known size) from the client side to the server side. Reference counting was used to communicate abort conditions to the other side (it did not work well because decreasing the reference count does not have any side-effects if the count remains positive). Direct calls between sides sometimes resulted in a call-me-when-I-am-calling-you "loops" and related bugs. BodyPipe is used to deliver request or response body (possibly of unknown size) from the body producer to the body consumer. A producer can be the client side (for virgin requests), the server side (for virgin replies), or the ICAP side (for adapted messages). A consumer can be the client side (for adapted responses, including responses in a request satisfaction mode), the server side (for adapted requests), and the ICAP side (for virgin requests and responses). BodyPipe uses asynchronous calls for communication between sides to avoid call-me-when-I-am-calling-you "loops". BodyPipe has methods to communicate normal termination and abort conditions to the other side. The use of those methods is mandatory. Reference counting is used only as a garbage collection mechanism. BodyPipe is used to read request bodies, including requests for which there is no consumer and the connection is in a 'closing' state. BodyPipe can auto-consume body so that a 'closing' connection does not have to rely on the body consumer presence when eating up remaining body data. If auto-consumption is turned on and the pipe starts consuming before a real consumer is attached to the pipe, the setConsumerIfNotLate call fails, and the real consumer has to handle the failure. The new BodyPipe approach should make support for HTTP/1.1 chunked requests easier. Only a few places in the pipe-related code assume that the request size is known. - Removed ClientBody as unused, replaced by BodyReader, then BodyPipe. - Moved HttpRequest::body_reader to HttpMsg::body_pipe so that all HTTP message bodies can be communicated via pipes. This is needed for the server side to supply response bodies to ICAP and for the ICAP side to supply adapted message bodies to others. - When cleaning HttpRequest or HttpReply, reset body_pipe to NULL instead of asserting that it is already NULL. BodyPipes are owned and maintained by other objects and HttpMsg is used only as a mechanism to pass the pipe pointer from the body producer to the consumer. To maintain guarantees similar to the old code, the BodyPipe destructor asserts that both the producer and the consumer are gone when the pipe is destructed. - When appending body data, do not append more than the known body size. This fixes the following assertion when POSTing from IE in my tests: assertion failed: client_side.cc:3205: "bodySizeLeft() > 0". I suspect IE or some Javascripts running on IE were appending extra CRLF to a POST, exposing the bug, and triggering the above assertion. - WARNING: Ftp-specific BodyPipe changes are untested, but the old code probably did not work well with ICAP either. More testing is needed. - Moved more common server-side code from http.* and ftp.* into Server.*. Most ICAP-related code is in the Server class now. The code move to the Server class and migration to BodyPipe exposed several FTP/HTTP inconsistencies and bugs. I marked those I could not fix with XXXs. - Distinguish the end of communication with the origin server from the end of communication with ICAP. Clean them up separately when possible. Terminate when both are completed (or aborted). - Polished persistentConnStatus() to avoid calling statusIfComplete() until really necessary (and appropriate). This makes debugging easier to understand for some. - Use auto-consumption feature to consume data from closing connections for which there is no real body consumer. - Use BodyPipe for maintaining the "closing" state of a connection instead of in.abortedSize. This change "removes" a few memory leaks and an assertion, but does need more work, especially when the regular BodyPipe consumer leaves early and does not consume the request body. - The client stream code sometimes marks the "closing" connection as STREAM_UNPLANNED_COMPLETE, leading to a double-close. I do not yet understand why. There is now code to ignore multiple attempts to enter the "closing" state. - ICAP improvements from the squid3-icap branch on SF, including: - Added icap_service_failure_limit squid.conf option. 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. The limit used to be hardcoded to 10. (based on the patch by Axel Westerhold) - Added icap_service_revival_delay squid.conf option. 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 [still] hardcoded minimum delay of 60 seconds. (based on the patch by Axel Westerhold) - Added icap_client_username_header and icap_client_username_encode squid.conf options to control how the authenticated client username should be sent to the ICAP service. (based on the patch by Axel Westerhold) - Handle REQMOD transaction failures where we cannot proceed with the normal request flow. - Use ICAPInitiator API to send "success" or "abort" messages to ICAP transaction initiator. Store virgin and adapted metadata as public fields (if the newly added ICAPInOut type) that the initiator can access when receiving our "successful adaptation" message. This keeps messages simple. - Using ICAPInitiator API and a "universal" BodyPipe API makes it possible to exchange bodies directly with client- or server-side code without ICAPClient* translators, which are now gone along with the ICAPInitXaction function in ICAPClient. - Added ICAPInitiator interface that classes initiating ICAP transactions must now support. The API currently has just two methods: one for receiving adapted message headers (indicating a successful ICAP transaction, at least to the point of fetching adapted headers) and one for receiving a notification of an aborted ICAP transaction. Most ICAP initiators (or their close relatives) will also need to implement BodyConsumer and/or BodyProducer APIs to exchange virgin and/or adapted HTTP message bodies with the initiated ICAP transaction. However, that activity is not addressed in this API. New AsyncCall API is used to declare the callback wrappers. - Use BodyPipe instead of MsgPipe for receiving virgin and sending adapted message bodies. BodyPipe is not much different from MsgPipeBody, but it is better to use a "universal" class that the rest of Squid code now uses. One complication is that BodyPipes are currently not created for messages with zero-size bodies. The code had to be changed to not assume that a zero-size body comes with a pipe. - Deleted MsgPipe and related classes. Message pipes had two purposes: coordinate HTTP message adaptation (start, get the adapted headers, abort) and exchange HTTP message bodies. The latter is now done via BodyPipe API. The former can be implemented directly in ICAPModXact. Deleted ICAPClient* and related classes as (my) design failure. The original idea behind message pipes and ICAPClient* classes was to isolate ICAP code from the Squid core. The core code was supposed to use ICAPClient* classes for all ICAP-related needs, and ICAPClient* classes were supposed to translate core needs into "ICAP needs" and use message pipes to communicate with asynchronously running ICAP transactions. The latter part worked fine, but the former did not. The core code still did a lot of ICAP-specific work on its own. This could be because ICAP processing affects the flow so much or because the core code had not been refactored enough to minimize ICAP interactions. Whatever the reason, we ended up with a lot of complex code/logic coordinating the core code and ICAPClient* classes. While ICAPClient* classes were "translating", they could not hide the key actions or events (such as message body exchange or transaction aborts) from the core. The core code still had to support those actions or handle those events. Thus, every major action or event was handled twice: once in the core side code and once in a ICAPClient* class. Removing ICAPClient* "translation" step simplified the code and possibly improved performance. As for the "ICAP separation" goal, the current exposure to the ICAPModXact class can be hidden by a generic "Message Adaptation Transaction" class if we need to support more adaptation protocols. The core code should not be affected much by such a change. - ClientHttpRequest: Support the new ICAPInitiator API and talk to ICAPModXact directly instead of using ICAPClient* classes, which are now gone. - ConnStateData: Use BodyPipe for delivering virgin request bodies to the server or ICAP side. Implement the BodyProducer interface. ClientHttpRequest: Use BodyPipe instead of BodyReader when receiving request bodies (from client side or ICAP). Implement the BodyConsumer interface. See the first BodyPipe CVS log message for the rationale. - Use BodyPipe for delivering virgin reply bodies to ICAP and receiving adapted reply bodies from ICAP. Implement the BodyProducer interface. Use BodyPipe instead of BodyReader when receiving request bodies (from client side or ICAP). Implement the BodyConsumer interface. - Replaced never-failing doIcap() with startIcap() that fails if we cannot select an ICAP service or the selected service is not usable. Rearranged ClientRequestContext::icapAclCheckDone() to bypass ICAP errors when possible. Now, ClientRequestContext::startIcap() is very similar to Server::startIcap(). Same for icapAclCheckDone(). Made ClientHttpRequest::handleIcapFailure() public because ClientRequestContext::icapAclCheckDone() calls it. - Polished TTL handling to make sure we use the default TTL when the ICAP server did not provide an explicit value or if we failed to communicate with the server. The latter case may not have been handled correctly before. - The minimum options update gap (currently hard-coded) must be smaller than the default options TTL. Otherwise, we get stale options and down ICAP services around the update time because we cannot update soon enough. - Support asynchronous transaction start. This allows for a better handling of startup errors (or at least makes them similar to other transaction errors). - Call a swanSong() method upon expected transaction termination (including aborts). This allows for proper and prompt [partial] transaction cleanup, without waiting for the destructor to be called. The destruction may be delayed by refcounting if we have other transaction users waiting for some transaction notifications. - Do not reuse a connection if we are still reading or writing (even if no actual I/O is scheduled). The old code would reuse such connections, and read/write leftovers from aborted transactions from/to the ICAP server. - Do not send last-chunk in ICAP Preview with a null-body. It is possible that the old code would send the last-chunk under some Preview conditions with null-body, but I am not sure. - Fixed HttpStateData memory leak visible when no RESPMOD services are enabled. ICAPAccessCheck constructor was cbdata-locking HttpStateData, but was not releasing the lock when there was no matching service class, leading to an HttpStateData leak. Furthermore, ICAPAccessCheck would then call HttpStateData back without validating the cbdata pointer, probably calling wrong or invalid HttpStateData. - Fixed "is it too late to bypass?" conditions in ClientHttpRequest::handleIcapFailure(). We should be able to bypass more often now. However, handleIcapFailure() still has the old bug: it does not check whether the service is optional. The current fix implies that now Squid may bypass essential services more often. - Call storeEntry()->complete() when ending request satisfaction. Without this call, we may keep the connection open, which does not work with responses that mark the end of their body by closing a connection. (Christos Tsantilas) - Fixed ieof condition detection. Squid was sending last-chunk without ieof bit and was sending two last chunks when doing preview (Tsantilas Christos). - When ICAP server wants the entire virgin body and sends 100 Continue after Preview, do not stop backing up virgin body data for echoing if we promised to support 204 No Content responses outside of Preview. If we allow 204s, 100 Continue may be followed by a 204 No Content and we will need the entire virgin body to echo back. - Rewrote MemBufClaim into a VirginBodyAct class to simplify and clarify code in hope to minimize the number of bugs like the one mentioned above. MemBufClaim was protecting an area of virgin body pipe content and, as a side effect, was providing the transaction with the content pointer for the write or send actions. Now VirginBodyAct just maintains the activity offset and the transaction code uses that to consume virgin body correctly. The size of the area is no longer maintained because it was usually unknown or unused; and when it was known and used (i.e., Preview), it could be taken from the preview state object anyway. Renamed and documented VirginBodyAct-related methods to clarify the intent. - When sending last-chunk in Preview, send ieof extension if we wrote the entire body. The old code would not send ieof if we wrote as many bytes as promised in the Preview header, even if we promised to write everything. This would mislead compliant ICAP servers that do not look at the Content-Length header and reply with 100 Continue, expecting more body data. - Do not reset Preview size to zero when expecting a virgin body of unknown size. A Squid user reported that this change works. - Polished debugging: Instead of using pointers, use unique ICAP transaction IDs. This helps with isolating a transaction in a large log, where pointers may be reused many times. Print connection descriptor like most of the core code does. Other minor improvements. --- diff --git a/src/HttpMsg.cc b/src/HttpMsg.cc index b6fc40324a..52f2f678d1 100644 --- a/src/HttpMsg.cc +++ b/src/HttpMsg.cc @@ -1,6 +1,6 @@ /* - * $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 @@ -45,6 +45,7 @@ HttpMsg::HttpMsg(http_hdr_owner_type owner): header(owner), HttpMsg::~HttpMsg() { assert(lock_count == 0); + assert(!body_pipe); } HttpMsgParseState &operator++ (HttpMsgParseState &aState) diff --git a/src/HttpMsg.h b/src/HttpMsg.h index efc6d64e71..aa1ba0c6be 100644 --- a/src/HttpMsg.h +++ b/src/HttpMsg.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -37,6 +37,7 @@ #include "typedefs.h" #include "HttpHeader.h" #include "HttpVersion.h" +#include "BodyPipe.h" // common parts of HttpRequest and HttpReply @@ -72,6 +73,8 @@ public: 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 diff --git a/src/HttpReply.cc b/src/HttpReply.cc index 22f7b64ef6..e641e4c4b2 100644 --- a/src/HttpReply.cc +++ b/src/HttpReply.cc @@ -1,6 +1,6 @@ /* - * $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 @@ -114,6 +114,10 @@ void HttpReply::reset() 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(); diff --git a/src/HttpRequest.cc b/src/HttpRequest.cc index cd766587cf..a68e28c245 100644 --- a/src/HttpRequest.cc +++ b/src/HttpRequest.cc @@ -1,6 +1,6 @@ /* - * $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 @@ -86,7 +86,7 @@ HttpRequest::init() 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 @@ -102,8 +102,9 @@ HttpRequest::init() 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(); @@ -333,6 +334,16 @@ request_flags::destinationIPLookedUp() const 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) diff --git a/src/HttpRequest.h b/src/HttpRequest.h index 9fd3e6c3d3..cffb1f90ed 100644 --- a/src/HttpRequest.h +++ b/src/HttpRequest.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -37,7 +37,6 @@ #include "HttpMsg.h" #include "client_side.h" #include "HierarchyLogEntry.h" -#include "BodyReader.h" #include "HttpRequestMethod.h" /* Http Request */ @@ -109,8 +108,6 @@ public: unsigned short client_port; - BodyReader::Pointer body_reader; - HierarchyLogEntry hier; err_type errType; diff --git a/src/ICAP/ICAPClient.cc b/src/ICAP/ICAPClient.cc index 3f1504b3fd..df2fd4966a 100644 --- a/src/ICAP/ICAPClient.cc +++ b/src/ICAP/ICAPClient.cc @@ -1,37 +1,11 @@ #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()); } diff --git a/src/ICAP/ICAPClient.h b/src/ICAP/ICAPClient.h index cb4c1e4647..daed777c86 100644 --- a/src/ICAP/ICAPClient.h +++ b/src/ICAP/ICAPClient.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -34,17 +34,9 @@ #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 */ diff --git a/src/ICAP/ICAPClientReqmodPrecache.cc b/src/ICAP/ICAPClientReqmodPrecache.cc deleted file mode 100644 index b66e34cf61..0000000000 --- a/src/ICAP/ICAPClientReqmodPrecache.cc +++ /dev/null @@ -1,170 +0,0 @@ -#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(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(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(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(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(data); - assert(icap->adapted != NULL); - icap->adapted->sendSinkNeed(); -} diff --git a/src/ICAP/ICAPClientReqmodPrecache.h b/src/ICAP/ICAPClientReqmodPrecache.h deleted file mode 100644 index d0285a5ea6..0000000000 --- a/src/ICAP/ICAPClientReqmodPrecache.h +++ /dev/null @@ -1,84 +0,0 @@ - -/* - * $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 */ diff --git a/src/ICAP/ICAPClientRespmodPrecache.cc b/src/ICAP/ICAPClientRespmodPrecache.cc deleted file mode 100644 index 7df4d6757e..0000000000 --- a/src/ICAP/ICAPClientRespmodPrecache.cc +++ /dev/null @@ -1,93 +0,0 @@ -#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(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 -} - diff --git a/src/ICAP/ICAPClientRespmodPrecache.h b/src/ICAP/ICAPClientRespmodPrecache.h deleted file mode 100644 index db4e10d63b..0000000000 --- a/src/ICAP/ICAPClientRespmodPrecache.h +++ /dev/null @@ -1,74 +0,0 @@ - -/* - * $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 */ diff --git a/src/ICAP/ICAPClientVector.cc b/src/ICAP/ICAPClientVector.cc deleted file mode 100644 index 16e34bfc5e..0000000000 --- a/src/ICAP/ICAPClientVector.cc +++ /dev/null @@ -1,172 +0,0 @@ -#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. -} diff --git a/src/ICAP/ICAPClientVector.h b/src/ICAP/ICAPClientVector.h deleted file mode 100644 index 462a53e406..0000000000 --- a/src/ICAP/ICAPClientVector.h +++ /dev/null @@ -1,101 +0,0 @@ - -/* - * $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 */ diff --git a/src/ICAP/ICAPConfig.cc b/src/ICAP/ICAPConfig.cc index d0480c0448..e4fd192bd0 100644 --- a/src/ICAP/ICAPConfig.cc +++ b/src/ICAP/ICAPConfig.cc @@ -1,6 +1,6 @@ /* - * $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/ * ---------------------------------------------------------- @@ -53,7 +53,7 @@ ICAPConfig::findService(const String& key) Vector::iterator iter = services.begin(); while (iter != services.end()) { - if (iter->getRaw()->key == key) + if ((*iter)->key == key) return *iter; ++iter; @@ -160,7 +160,7 @@ ICAPAccessCheck::check() */ 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; } @@ -247,27 +247,24 @@ ICAPAccessCheck::do_callback() 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 @@ -318,7 +315,7 @@ ICAPAccessCheck::findBestService(ICAPClass *c, bool preferUp) { return service; } - if (secondBest.getRaw()) { + if (secondBest != NULL) { what = "down "; debugs(93,5,HERE << "found first matching " << what << "service in class " << c->key << diff --git a/src/ICAP/ICAPConfig.h b/src/ICAP/ICAPConfig.h index 34a11d4b8f..391b199538 100644 --- a/src/ICAP/ICAPConfig.h +++ b/src/ICAP/ICAPConfig.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -43,8 +43,6 @@ class acl_access; class ConfigParser; -class ICAPConfig; - class ICAPClass { @@ -102,6 +100,10 @@ public: 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 services; Vector classes; @@ -126,4 +128,6 @@ public: }; +extern ICAPConfig TheICAPConfig; + #endif /* SQUID_ICAPCONFIG_H */ diff --git a/src/ICAP/ICAPElements.h b/src/ICAP/ICAPElements.h index 066f9e8c58..75e18ac427 100644 --- a/src/ICAP/ICAPElements.h +++ b/src/ICAP/ICAPElements.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -44,9 +44,6 @@ struct ICAP 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); diff --git a/src/ICAP/MsgPipeData.h b/src/ICAP/ICAPInOut.h similarity index 67% rename from src/ICAP/MsgPipeData.h rename to src/ICAP/ICAPInOut.h index d30beb5c15..f3fe51444f 100644 --- a/src/ICAP/MsgPipeData.h +++ b/src/ICAP/ICAPInOut.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -31,36 +31,30 @@ * */ -#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) @@ -73,23 +67,24 @@ public: } } - 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 */ diff --git a/src/ICAP/ICAPInitiator.cc b/src/ICAP/ICAPInitiator.cc new file mode 100644 index 0000000000..193870455d --- /dev/null +++ b/src/ICAP/ICAPInitiator.cc @@ -0,0 +1 @@ +// XXX: remove me! diff --git a/src/ICAP/MsgPipeSink.h b/src/ICAP/ICAPInitiator.h similarity index 67% rename from src/ICAP/MsgPipeSink.h rename to src/ICAP/ICAPInitiator.h index f5c65cfaab..365c053018 100644 --- a/src/ICAP/MsgPipeSink.h +++ b/src/ICAP/ICAPInitiator.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -31,26 +31,29 @@ * */ -#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 */ diff --git a/src/ICAP/ICAPModXact.cc b/src/ICAP/ICAPModXact.cc index 253d60da25..a2566b4ae0 100644 --- a/src/ICAP/ICAPModXact.cc +++ b/src/ICAP/ICAPModXact.cc @@ -4,11 +4,11 @@ #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" @@ -21,14 +21,11 @@ // 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; @@ -38,34 +35,22 @@ ICAPModXact::State::State() 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 @@ -76,16 +61,15 @@ void ICAPModXact::init(ICAPServiceRep::Pointer &aService, MsgPipe::Pointer &aVir 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(virgin->data->body->max_capacity)); + ICAPXaction::start(); estimateVirginBody(); // before virgin disappears! @@ -99,6 +83,7 @@ void ICAPModXact::noteSourceStart(MsgPipe *p) // 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(); } @@ -113,7 +98,7 @@ void ICAPModXact_noteServiceReady(void *data, ICAPServiceRep::Pointer &) 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); } @@ -151,13 +136,12 @@ void ICAPModXact::handleCommConnected() 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) @@ -174,18 +158,24 @@ void ICAPModXact::handleCommWroteHeaders() { 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; @@ -208,7 +198,7 @@ void ICAPModXact::writeMore() return; case State::writingPreview: - writePriviewBody(); + writePreviewBody(); return; case State::writingPrime: @@ -220,19 +210,21 @@ void ICAPModXact::writeMore() } } -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); @@ -244,14 +236,13 @@ void ICAPModXact::writePriviewBody() 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); } } @@ -259,6 +250,7 @@ void ICAPModXact::writePrimeBody() 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); @@ -266,23 +258,31 @@ void ICAPModXact::writeSomeBody(const char *label, size_t size) 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); } @@ -297,25 +297,6 @@ void ICAPModXact::writeSomeBody(const char *label, size_t size) } } -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(); @@ -333,46 +314,59 @@ void ICAPModXact::closeChunk(MemBuf &buf) 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(buf.contentSize()); + if (!virgin.body_pipe) + return; + + BodyPipe &bp = *virgin.body_pipe; + const size_t have = static_cast(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(); } } @@ -391,44 +385,44 @@ void ICAPModXact::stopWriting(bool nicely) 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(); } @@ -436,9 +430,8 @@ void ICAPModXact::startReading() { 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(); @@ -452,8 +445,9 @@ void ICAPModXact::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; } @@ -474,37 +468,36 @@ void ICAPModXact::handleCommRead(size_t) 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(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; } @@ -514,38 +507,32 @@ void ICAPModXact::stopSending(bool nicely) 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() @@ -564,13 +551,13 @@ 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()"); } @@ -594,7 +581,8 @@ void ICAPModXact::parseHeaders() return; } - adapted->sendSourceStart(); + AsyncCall(93,5, initiator, ICAPInitiator::noteIcapHeadersAdapted); + cbdataReferenceDone(initiator); if (state.sending == State::sendingVirgin) echoMore(); @@ -668,10 +656,11 @@ bool ICAPModXact::validate200Ok() 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 @@ -687,21 +676,19 @@ void ICAPModXact::handle200Ok() 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; @@ -711,18 +698,18 @@ void ICAPModXact::handle204NoContent() 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(oldHead)) { HttpRequest *newR = new HttpRequest; - newR->client_addr = oldR->client_addr; + inheritVirginProperties(*newR, *oldR); newHead = newR; } else if (dynamic_cast(oldHead)) newHead = new HttpReply; Must(newHead); - adapted->data->setHeader(newHead); + adapted.setHeader(newHead); // parse the buffer back http_status error = HTTP_STATUS_NONE; @@ -733,7 +720,27 @@ void ICAPModXact::handle204NoContent() 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() @@ -751,11 +758,21 @@ void ICAPModXact::parseHttpHead() 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(adapted.header)) { + const HttpRequest *oldR = dynamic_cast(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 @@ -780,39 +797,59 @@ bool ICAPModXact::parseHead(HttpMsg *head) 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()); @@ -824,11 +861,10 @@ bool ICAPModXact::parsePresentBody() 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() @@ -836,7 +872,7 @@ 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; @@ -846,11 +882,10 @@ void ICAPModXact::stopParsing() } // 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) @@ -860,12 +895,11 @@ void ICAPModXact::noteSourceProgress(MsgPipe *p) } // 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(); @@ -876,22 +910,34 @@ void ICAPModXact::noteSourceFinish(MsgPipe *p) 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(); @@ -903,21 +949,26 @@ void ICAPModXact::noteSinkNeed(MsgPipe *p) 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(); @@ -929,23 +980,7 @@ void ICAPModXact::doStop() 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) @@ -970,11 +1005,11 @@ 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(virgin->data->header); + const HttpRequest *request = virgin.cause ? + virgin.cause : + dynamic_cast(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) { @@ -983,11 +1018,11 @@ void ICAPModXact::makeRequestHeaders(MemBuf &buf) 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()) @@ -1001,13 +1036,18 @@ void ICAPModXact::makeRequestHeaders(MemBuf &buf) 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) @@ -1016,9 +1056,7 @@ void ICAPModXact::makeRequestHeaders(MemBuf &buf) 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()); @@ -1030,6 +1068,17 @@ void ICAPModXact::makeRequestHeaders(MemBuf &buf) 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 @@ -1050,13 +1099,13 @@ void ICAPModXact::packHead(MemBuf &httpBuf, const HttpMsg *head) // 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; @@ -1067,17 +1116,19 @@ bool ICAPModXact::shouldPreview(const String &urlPath) // 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 @@ -1099,6 +1150,26 @@ bool ICAPModXact::shouldAllow204() 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); @@ -1106,10 +1177,10 @@ void ICAPModXact::fillPendingStatus(MemBuf &buf) const 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) @@ -1120,7 +1191,7 @@ void ICAPModXact::fillPendingStatus(MemBuf &buf) const buf.Printf("P(%d)", (int) preview.debt()); } - if (virginSendClaim.active()) + if (virginBodySending.active()) buf.append("B", 1); if (!state.doneParsing() && state.parsing != State::psIcapHeader) @@ -1134,7 +1205,7 @@ void ICAPModXact::fillDoneStatus(MemBuf &buf) const { ICAPXaction::fillDoneStatus(buf); - if (state.doneReceiving) + if (!virgin.body_pipe) buf.append("R", 1); if (state.doneWriting()) @@ -1163,33 +1234,57 @@ bool ICAPModXact::gotEncapsulated(const char *section) const // 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(virgin->data-> - header)) - method = req->method; - else - return; + if (HttpRequest *req = dynamic_cast(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(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 @@ -1221,55 +1316,33 @@ size_t SizedEstimate::size() const -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(aGoal) : - XMIN(static_cast(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(size); - - if (limited() && theStart >= theGoal) - disable(); } -size_t MemBufClaim::offset() const +size_t VirginBodyAct::offset() const { Must(active()); return static_cast(theStart); } -bool MemBufClaim::limited() const -{ - Must(active()); - return theGoal >= 0; -} - ICAPPreview::ICAPPreview(): theWritten(0), theAd(0), theState(stDisabled) {} @@ -1315,27 +1388,24 @@ size_t ICAPPreview::debt() const 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; } diff --git a/src/ICAP/ICAPModXact.h b/src/ICAP/ICAPModXact.h index 35867fd475..2ea49d222f 100644 --- a/src/ICAP/ICAPModXact.h +++ b/src/ICAP/ICAPModXact.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -35,15 +35,19 @@ #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; @@ -67,32 +71,31 @@ private: 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 @@ -118,27 +121,29 @@ private: 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 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(); @@ -150,8 +155,15 @@ public: // service waiting void noteServiceReady(); +public: + ICAPInOut virgin; + ICAPInOut adapted; + private: + virtual void start(); + void estimateVirginBody(); + void makeAdaptedBodyPipe(const char *what); void waitForService(); @@ -160,7 +172,7 @@ private: void startWriting(); void writeMore(); - void writePriviewBody(); + void writePreviewBody(); void writePrimeBody(); void writeSomeBody(const char *label, size_t size); @@ -169,14 +181,17 @@ private: 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(); @@ -189,9 +204,10 @@ private: 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(); @@ -203,8 +219,8 @@ private: void echoMore(); virtual bool doneAll() const; + virtual void swanSong(); - virtual void doStop(); void stopReceiving(); void stopSending(bool nicely); void stopWriting(bool nicely); @@ -219,17 +235,16 @@ private: 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 @@ -242,15 +257,16 @@ private: 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; } diff --git a/src/ICAP/ICAPOptXact.cc b/src/ICAP/ICAPOptXact.cc index 911d3c840b..2353c41043 100644 --- a/src/ICAP/ICAPOptXact.cc +++ b/src/ICAP/ICAPOptXact.cc @@ -12,25 +12,29 @@ 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(); @@ -50,32 +54,6 @@ void ICAPOptXact::handleCommConnected() 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(); @@ -93,13 +71,16 @@ void ICAPOptXact::handleCommWrote(size_t size) // 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()); @@ -108,19 +89,42 @@ bool ICAPOptXact::parseResponse() 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()); } diff --git a/src/ICAP/ICAPOptXact.h b/src/ICAP/ICAPOptXact.h index 8fce3de9a6..45f3e6f7ed 100644 --- a/src/ICAP/ICAPOptXact.h +++ b/src/ICAP/ICAPOptXact.h @@ -1,5 +1,5 @@ /* - * $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/ @@ -46,33 +46,31 @@ class ICAPOptXact: public ICAPXaction { 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 */ diff --git a/src/ICAP/ICAPOptions.cc b/src/ICAP/ICAPOptions.cc index 41cffef587..c28abaec70 100644 --- a/src/ICAP/ICAPOptions.cc +++ b/src/ICAP/ICAPOptions.cc @@ -57,10 +57,16 @@ bool ICAPOptions::fresh() const 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) @@ -92,9 +98,6 @@ 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) diff --git a/src/ICAP/ICAPOptions.h b/src/ICAP/ICAPOptions.h index 58c3f9a632..d18c81ed8d 100644 --- a/src/ICAP/ICAPOptions.h +++ b/src/ICAP/ICAPOptions.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -35,7 +35,7 @@ #define SQUID_ICAPOPTIONS_H #include "squid.h" -#include "ICAPClient.h" +#include "ICAPServiceRep.h" class wordlist; @@ -57,11 +57,10 @@ public: 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; diff --git a/src/ICAP/ICAPServiceRep.cc b/src/ICAP/ICAPServiceRep.cc index 6c9c6ff630..ab38ba94d4 100644 --- a/src/ICAP/ICAPServiceRep.cc +++ b/src/ICAP/ICAPServiceRep.cc @@ -8,13 +8,11 @@ #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), @@ -177,9 +175,10 @@ void ICAPServiceRep::invalidate() 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 @@ -301,6 +300,9 @@ void ICAPServiceRep::noteTimeToNotify() 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 @@ -333,8 +335,8 @@ bool ICAPServiceRep::needNewOptions() const 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; @@ -385,9 +387,13 @@ void ICAPServiceRep::checkOptions() /* * 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 @@ -398,28 +404,28 @@ void ICAPServiceRep::announceStatusChange(const char *downPhrase, bool important 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(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()); @@ -433,8 +439,8 @@ void ICAPServiceRep::startGettingOptions() 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? } @@ -454,31 +460,32 @@ void ICAPServiceRep::scheduleUpdate() 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; @@ -494,11 +501,22 @@ const char *ICAPServiceRep::status() const 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); @@ -507,10 +525,7 @@ const char *ICAPServiceRep::status() const 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(); diff --git a/src/ICAP/ICAPServiceRep.h b/src/ICAP/ICAPServiceRep.h index 4f9af4a1c8..a1cd308627 100644 --- a/src/ICAP/ICAPServiceRep.h +++ b/src/ICAP/ICAPServiceRep.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -114,7 +114,7 @@ public: 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 diff --git a/src/ICAP/ICAPXaction.cc b/src/ICAP/ICAPXaction.cc index c87aeaad7d..359e845de3 100644 --- a/src/ICAP/ICAPXaction.cc +++ b/src/ICAP/ICAPXaction.cc @@ -4,21 +4,22 @@ #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) { @@ -58,7 +59,15 @@ void ICAPXaction_noteCommRead(int, char *, size_t size, comm_err_t status, int x 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), @@ -68,19 +77,26 @@ ICAPXaction::ICAPXaction(const char *aTypeName): 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(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(readBuf.potentialSpaceSize()) <= commBufSize); } // TODO: obey service-specific, OPTIONS-reported connection limit @@ -92,6 +108,7 @@ void ICAPXaction::openConnection() if (connection >= 0) { debugs(93,3, HERE << "reused pconn FD " << connection); + connector = &ICAPXaction_noteCommConnected; // make doneAll() false eventAdd("ICAPXaction::reusedConnection", reusedConnection, this, @@ -130,10 +147,6 @@ ICAPXaction::reusedConnection(void *data) { 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); } @@ -148,17 +161,17 @@ void ICAPXaction::closeConnection() 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); } @@ -388,7 +401,7 @@ bool ICAPXaction::doneWithIo() const 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; @@ -398,12 +411,22 @@ void ICAPXaction::mustStop(const char *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) @@ -421,13 +444,21 @@ ICAPServiceRep &ICAPXaction::service() 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; } @@ -437,7 +468,7 @@ bool ICAPXaction::callStart(const char *method) 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 @@ -447,17 +478,23 @@ void ICAPXaction::callException(const TextException &e) 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; } @@ -467,13 +504,13 @@ const char *ICAPXaction::status() const 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(); @@ -483,7 +520,7 @@ const char *ICAPXaction::status() const 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); @@ -491,7 +528,7 @@ void ICAPXaction::fillPendingStatus(MemBuf &buf) const if (reader) buf.append("r", 1); - buf.append(")", 1); + buf.append(";", 1); } } diff --git a/src/ICAP/ICAPXaction.h b/src/ICAP/ICAPXaction.h index b4fac40b87..c4a7c8f798 100644 --- a/src/ICAP/ICAPXaction.h +++ b/src/ICAP/ICAPXaction.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -37,14 +37,21 @@ #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 @@ -54,6 +61,10 @@ class ICAPXaction: public RefCountable public: typedef RefCount 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(); @@ -65,6 +76,10 @@ public: 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); @@ -95,9 +110,11 @@ protected: 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; @@ -107,6 +124,9 @@ protected: 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 /* @@ -142,6 +162,7 @@ protected: 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 diff --git a/src/ICAP/MsgPipe.cc b/src/ICAP/MsgPipe.cc deleted file mode 100644 index 83f237c874..0000000000 --- a/src/ICAP/MsgPipe.cc +++ /dev/null @@ -1,99 +0,0 @@ -#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(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; -} diff --git a/src/ICAP/MsgPipe.h b/src/ICAP/MsgPipe.h deleted file mode 100644 index 7fd5ac8c58..0000000000 --- a/src/ICAP/MsgPipe.h +++ /dev/null @@ -1,89 +0,0 @@ - -/* - * $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 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 */ diff --git a/src/ICAP/MsgPipeEnd.h b/src/ICAP/MsgPipeEnd.h deleted file mode 100644 index 4fb74ccda4..0000000000 --- a/src/ICAP/MsgPipeEnd.h +++ /dev/null @@ -1,50 +0,0 @@ - -/* - * $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 */ diff --git a/src/ICAP/MsgPipeSource.h b/src/ICAP/MsgPipeSource.h deleted file mode 100644 index 97a7d6f125..0000000000 --- a/src/ICAP/MsgPipeSource.h +++ /dev/null @@ -1,54 +0,0 @@ - -/* - * $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 */ diff --git a/src/Makefile.am b/src/Makefile.am index 5336b586ee..4f7e533b7c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ # # 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: # @@ -411,6 +411,8 @@ squid_SOURCES = \ ACLChecklist.h \ $(squid_ACLSOURCES) \ asn.cc \ + AsyncCall.cc \ + AsyncCall.h \ AsyncEngine.cc \ AsyncEngine.h \ authenticate.cc \ @@ -429,8 +431,8 @@ squid_SOURCES = \ 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 \ @@ -669,12 +671,9 @@ ICAP_libicap_a_SOURCES = \ 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 \ @@ -689,12 +688,6 @@ ICAP_libicap_a_SOURCES = \ 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 @@ -739,7 +732,10 @@ ufsdump_SOURCES = \ time.cc \ ufsdump.cc \ url.cc \ - BodyReader.cc \ + AsyncCall.cc \ + AsyncCall.h \ + BodyPipe.cc \ + BodyPipe.h \ ConfigParser.cc \ store.cc \ StoreFileSystem.cc \ @@ -1254,7 +1250,7 @@ tests_testCacheManager_SOURCES = \ ACLRegexData.cc \ ACLUserData.cc \ authenticate.cc \ - BodyReader.cc \ + BodyPipe.cc \ cache_manager.cc \ cache_cf.cc \ CacheDigest.cc \ @@ -1418,7 +1414,7 @@ tests_testEvent_SOURCES = \ ACLRegexData.cc \ ACLUserData.cc \ authenticate.cc \ - BodyReader.cc \ + BodyPipe.cc \ cache_manager.cc \ cache_cf.cc \ CacheDigest.cc \ @@ -1568,7 +1564,7 @@ tests_testEventLoop_SOURCES = \ ACLRegexData.cc \ ACLUserData.cc \ authenticate.cc \ - BodyReader.cc \ + BodyPipe.cc \ cache_manager.cc \ cache_cf.cc \ CacheDigest.cc \ @@ -1742,7 +1738,7 @@ tests_test_http_range_SOURCES = \ ACLRegexData.cc \ ACLUserData.cc \ authenticate.cc \ - BodyReader.cc \ + BodyPipe.cc \ cache_cf.cc \ cache_manager.cc \ CacheDigest.cc \ @@ -1898,7 +1894,7 @@ tests_testHttpRequest_SOURCES = \ ACLRegexData.cc \ ACLUserData.cc \ authenticate.cc \ - BodyReader.cc \ + BodyPipe.cc \ cache_manager.cc \ cache_cf.cc \ CacheDigest.cc \ @@ -2233,7 +2229,7 @@ tests_testURL_SOURCES = \ ACLRegexData.cc \ ACLUserData.cc \ authenticate.cc \ - BodyReader.cc \ + BodyPipe.cc \ cache_manager.cc \ cache_cf.cc \ CacheDigest.cc \ diff --git a/src/Server.cc b/src/Server.cc index 2566208a9d..2a390adcf0 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -1,5 +1,5 @@ /* - * $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 @@ -37,18 +37,16 @@ #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); } @@ -61,15 +59,238 @@ ServerStateData::~ServerStateData() 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(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. @@ -77,7 +298,7 @@ ServerStateData::~ServerStateData() * 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) { @@ -88,9 +309,178 @@ ServerStateData::startIcap(ICAPServiceRep::Pointer 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(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 diff --git a/src/Server.h b/src/Server.h index 5fb82c160d..c8f03745e5 100644 --- a/src/Server.h +++ b/src/Server.h @@ -1,6 +1,6 @@ /* - * $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 * @@ -33,55 +33,132 @@ */ /* - * 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 */ diff --git a/src/cf.data.pre b/src/cf.data.pre index 33e851013f..d4ba54fd66 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -1,6 +1,6 @@ # -# $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/ @@ -4968,6 +4968,39 @@ DOC_START 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 @@ -5028,8 +5061,29 @@ COMMENT: on|off 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 diff --git a/src/client_side.cc b/src/client_side.cc index 0b533cd1d6..6176c8f99c 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -1,6 +1,6 @@ /* - * $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 @@ -134,8 +134,6 @@ static ClientSocketContext *parseHttpRequest(ConnStateData::Pointer &, HttpParse #if USE_IDENT static IDCB clientIdentDone; #endif -static BodyReadFunc clientReadBody; -static BodyAbortFunc clientAbortBody; static CSCB clientSocketRecipient; static CSD clientSocketDetach; static void clientSetKeepaliveFlag(ClientHttpRequest *); @@ -494,6 +492,8 @@ ClientHttpRequest::logRequest() 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()); @@ -577,23 +577,6 @@ ConnStateData::areAllContextsForThisConnection() const 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() { @@ -653,7 +636,10 @@ ConnStateData::~ConnStateData() cbdataReferenceDone(port); - body_reader(NULL); // refcounted + if (bodyPipe != NULL) { + bodyPipe->clearProducer(false); + bodyPipe = NULL; // refcounted + } } /* @@ -1560,14 +1546,24 @@ ClientSocketContext::doClose() } 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 @@ -1576,20 +1572,7 @@ ClientSocketContext::initiateClose() * 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; } } @@ -1610,9 +1593,12 @@ ClientSocketContext::writeComplete(int fd, char *bufnotused, size_t size, comm_e 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; } @@ -1632,7 +1618,7 @@ ClientSocketContext::writeComplete(int fd, char *bufnotused, size_t size, comm_e /* fallthrough */ case STREAM_FAILED: - initiateClose(); + initiateClose("STREAM_UNPLANNED_COMPLETE|STREAM_FAILED"); return; default: @@ -1857,7 +1843,7 @@ parseHttpRequest(ConnStateData::Pointer & conn, HttpParser *hp, method_t * metho 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"); @@ -1894,9 +1880,9 @@ parseHttpRequest(ConnStateData::Pointer & conn, HttpParser *hp, method_t * metho /* 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"); } @@ -1921,7 +1907,7 @@ parseHttpRequest(ConnStateData::Pointer & conn, HttpParser *hp, method_t * metho 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"); } @@ -2138,6 +2124,8 @@ clientProcessRequest(ConnStateData::Pointer &conn, HttpParser *hp, ClientSocketC { 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(); @@ -2152,7 +2140,7 @@ clientProcessRequest(ConnStateData::Pointer &conn, HttpParser *hp, ClientSocketC assert(context->http->out.offset == 0); context->pullData(); conn->flags.readMoreRequests = false; - goto finish; + goto finish; } if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) { @@ -2166,7 +2154,7 @@ clientProcessRequest(ConnStateData::Pointer &conn, HttpParser *hp, ClientSocketC assert(context->http->out.offset == 0); context->pullData(); conn->flags.readMoreRequests = false; - goto finish; + goto finish; } /* compile headers */ @@ -2183,7 +2171,7 @@ clientProcessRequest(ConnStateData::Pointer &conn, HttpParser *hp, ClientSocketC assert(context->http->out.offset == 0); context->pullData(); conn->flags.readMoreRequests = false; - goto finish; + goto finish; } request->flags.accelerated = http->flags.accel; @@ -2226,7 +2214,7 @@ clientProcessRequest(ConnStateData::Pointer &conn, HttpParser *hp, ClientSocketC assert(context->http->out.offset == 0); context->pullData(); conn->flags.readMoreRequests = false; - goto finish; + goto finish; } @@ -2240,29 +2228,22 @@ clientProcessRequest(ConnStateData::Pointer &conn, HttpParser *hp, ClientSocketC 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? */ @@ -2278,7 +2259,7 @@ clientProcessRequest(ConnStateData::Pointer &conn, HttpParser *hp, ClientSocketC assert(context->http->out.offset == 0); context->pullData(); conn->flags.readMoreRequests = false; - goto finish; + goto finish; } context->mayUseConnection(true); @@ -2293,8 +2274,8 @@ clientProcessRequest(ConnStateData::Pointer &conn, HttpParser *hp, ClientSocketC http->doCallouts(); finish: - /* Consume request buffer */ - connNoteUseOfBuffer(conn.getRaw(), http->req_sz); + if (!notedUseOfBuffer) + connNoteUseOfBuffer(conn.getRaw(), http->req_sz); } static void @@ -2330,8 +2311,9 @@ connOkToAddRequest(ConnStateData::Pointer &conn) 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; } @@ -2357,9 +2339,9 @@ clientParseRequest(ConnStateData::Pointer conn, bool &do_next_read) 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 */ @@ -2371,13 +2353,13 @@ clientParseRequest(ConnStateData::Pointer conn, bool &do_next_read) /* 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) { @@ -2450,45 +2432,8 @@ clientReadRequest(int fd, char *buf, size_t size, comm_err_t flag, int xerrno, 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); @@ -2539,67 +2484,65 @@ clientReadRequest(int fd, char *buf, size_t size, comm_err_t flag, int xerrno, } } -/* - * 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 */ @@ -3234,17 +3177,40 @@ ConnStateData::reading(bool const newBool) 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 * diff --git a/src/client_side.h b/src/client_side.h index b94735c802..ad79fe414e 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -36,7 +36,7 @@ #include "comm.h" #include "StoreIOBuffer.h" -#include "BodyReader.h" +#include "BodyPipe.h" #include "RefCount.h" class ConnStateData; @@ -119,14 +119,14 @@ private: 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: @@ -157,15 +157,6 @@ 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(); @@ -206,16 +197,16 @@ public: 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); @@ -223,7 +214,7 @@ private: 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 */ diff --git a/src/client_side_request.cc b/src/client_side_request.cc index 2ec0718ab0..5242f30d46 100644 --- a/src/client_side_request.cc +++ b/src/client_side_request.cc @@ -1,6 +1,6 @@ /* - * $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) @@ -59,7 +59,7 @@ #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); @@ -120,7 +120,7 @@ ClientRequestContext::~ClientRequestContext() } } - 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) @@ -156,7 +156,6 @@ ClientHttpRequest::ClientHttpRequest(ConnStateData::Pointer aConn) : loggingEntr setConn(aConn); dlinkAdd(this, &active, &ClientActiveRequests); #if ICAP_CLIENT - request_satisfaction_mode = false; #endif } @@ -238,14 +237,10 @@ ClientHttpRequest::~ClientHttpRequest() { 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 @@ -262,10 +257,12 @@ ClientHttpRequest::~ClientHttpRequest() freeResources(); #if ICAP_CLIENT - - if (icap) - delete icap; - + if (icapHeadSource != NULL) { + icapHeadSource->noteInitiatorAborted(); + icapHeadSource = NULL; + } + if (icapBodySource != NULL) + stopConsumingFrom(icapBodySource); #endif if (calloutContext) @@ -514,35 +511,20 @@ void 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 @@ -851,10 +833,10 @@ ClientRequestContext::clientRedirectDone(char *result) ; } - 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; @@ -867,7 +849,7 @@ ClientRequestContext::clientRedirectDone(char *result) /* 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); @@ -1088,140 +1070,40 @@ ClientHttpRequest::doCallouts() #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(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(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); @@ -1235,6 +1117,12 @@ ClientHttpRequest::takeAdaptedHeaders(HttpMsg *msg) } else if (HttpReply *new_rep = dynamic_cast(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(node->data.getRaw()); repContext->createStoreEntry(request->method, request->flags); @@ -1246,71 +1134,109 @@ ClientHttpRequest::takeAdaptedHeaders(HttpMsg *msg) 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(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(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 diff --git a/src/client_side_request.h b/src/client_side_request.h index 535f634147..671329e0ab 100644 --- a/src/client_side_request.h +++ b/src/client_side_request.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -44,8 +44,8 @@ #if ICAP_CLIENT #include "ICAP/ICAPServiceRep.h" - -class ICAPClientReqmodPrecache; +#include "ICAP/ICAPInitiator.h" +#include "ICAP/ICAPModXact.h" class HttpMsg; #endif @@ -60,6 +60,10 @@ class ConnStateData; 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: @@ -158,15 +162,25 @@ private: #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 diff --git a/src/forward.cc b/src/forward.cc index 5ead657776..7d114230e7 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -1,6 +1,6 @@ /* - * $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 @@ -430,7 +430,7 @@ FwdState::checkRetriable() * 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 */ diff --git a/src/ftp.cc b/src/ftp.cc index cd70c4d494..b18f8a0cd3 100644 --- a/src/ftp.cc +++ b/src/ftp.cc @@ -1,6 +1,6 @@ /* - * $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 @@ -56,8 +56,8 @@ #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 @@ -107,6 +107,7 @@ struct _ftp_flags bool put_mkdir; bool listformat_unknown; bool listing_started; + bool completed_forwarding; }; class FtpStateData; @@ -191,6 +192,7 @@ public: void listingFinish(); void scheduleReadControlReply(int); void handleControlReply(); + void readStor(); char *htmlifyListEntry(const char *line); void parseListing(); void dataComplete(); @@ -200,10 +202,11 @@ public: 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); @@ -211,27 +214,27 @@ public: 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 }; @@ -450,7 +453,7 @@ FtpStateData::~FtpStateData() safe_free(dirpath); safe_free(data.host); - /* XXX this is also set to NULL in transactionForwardComplete */ + fwd = NULL; // refcounted } @@ -1103,6 +1106,12 @@ FtpStateData::parseListing() 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 */ @@ -1152,15 +1161,14 @@ FtpStateData::parseListing() 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)); @@ -1171,6 +1179,11 @@ FtpStateData::parseListing() xfree(sbuf); } +int +FtpStateData::dataDescriptor() const { + return data.fd; +} + void FtpStateData::dataComplete() { @@ -1199,7 +1212,7 @@ FtpStateData::dataReadWrapper(int fd, char *buf, size_t len, comm_err_t errflag, } void -FtpStateData::maybeReadData() +FtpStateData::maybeReadVirginBody() { if (data.fd < 0) return; @@ -1210,14 +1223,8 @@ FtpStateData::maybeReadData() 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"); @@ -1259,7 +1266,7 @@ FtpStateData::dataRead(int fd, char *buf, size_t len, comm_err_t errflag, int xe #endif if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { - transactionAbort(); + abortTransaction("entry aborted during dataRead"); return; } @@ -1293,7 +1300,7 @@ FtpStateData::dataRead(int fd, char *buf, size_t len, comm_err_t errflag, int xe 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 */ @@ -1339,7 +1346,7 @@ FtpStateData::processReplyBody() storeBufferFlush(entry); - maybeReadData(); + maybeReadVirginBody(); } /* @@ -1485,7 +1492,7 @@ FtpStateData::start() entry->replaceHttpReply(reply); - transactionComplete(); + serverComplete(); return; } @@ -1710,7 +1717,7 @@ FtpStateData::ftpReadControlReply(int fd, char *buf, size_t len, comm_err_t errf return; if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { - ftpState->transactionAbort(); + ftpState->abortTransaction("entry aborted during control reply read"); return; } @@ -1742,8 +1749,8 @@ FtpStateData::ftpReadControlReply(int fd, char *buf, size_t len, comm_err_t errf 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; } @@ -2194,7 +2201,7 @@ ftpSendPasv(FtpStateData * ftpState) */ if (!EBIT_TEST(ftpState->entry->flags, ENTRY_ABORTED)) - ftpState->transactionForwardComplete(); + ftpState->completeForwarding(); ftpSendQuit(ftpState); @@ -2475,7 +2482,7 @@ ftpAcceptDataConnection(int fd, int newfd, ConnectionDetail *details, return; if (EBIT_TEST(ftpState->entry->flags, ENTRY_ABORTED)) { - ftpState->transactionAbort(); + ftpState->abortTransaction("entry aborted when accepting data conn"); return; } @@ -2567,33 +2574,42 @@ ftpSendStor(FtpStateData * ftpState) 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); } } @@ -2684,7 +2700,7 @@ ftpReadList(FtpStateData * ftpState) 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 @@ -2729,7 +2745,7 @@ ftpReadRetr(FtpStateData * ftpState) /* 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 @@ -2780,55 +2796,22 @@ ftpReadTransferDone(FtpStateData * ftpState) } } -/* 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 @@ -2860,8 +2843,8 @@ ftpSendQuit(FtpStateData * ftpState) 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 @@ -2954,7 +2937,7 @@ FtpStateData::failed(err_type error, int xerrno) if (entry->isEmpty()) failedErrorMessage(error, xerrno); - transactionComplete(); + serverComplete(); } void @@ -3257,13 +3240,17 @@ 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"); @@ -3273,48 +3260,36 @@ FtpStateData::writeReplyBody(const char *data, int len) 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); @@ -3327,19 +3302,27 @@ FtpStateData::transactionComplete() 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 @@ -3351,12 +3334,13 @@ icapAclCheckDoneWrapper(ICAPServiceRep::Pointer service, void *data) 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 @@ -3375,109 +3359,7 @@ FtpStateData::icapAclCheckDone(ICAPServiceRep::Pointer service) 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 diff --git a/src/http.cc b/src/http.cc index 14d9459b53..6c7dd5073a 100644 --- a/src/http.cc +++ b/src/http.cc @@ -1,6 +1,6 @@ /* - * $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 @@ -56,7 +56,6 @@ #include "DelayPools.h" #endif #if ICAP_CLIENT -#include "ICAP/ICAPClientRespmodPrecache.h" #include "ICAP/ICAPConfig.h" extern ICAPConfig TheICAPConfig; #endif @@ -75,7 +74,8 @@ static void copyOneHeaderFromClientsideRequestToUpstreamRequest(const HttpHeader 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; @@ -141,11 +141,6 @@ HttpStateData::~HttpStateData() * 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(); @@ -153,7 +148,12 @@ HttpStateData::~HttpStateData() 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 @@ -161,9 +161,7 @@ httpStateFree(int fd, void *data) { HttpStateData *httpState = static_cast(data); debug(11,5)("httpStateFree: FD %d, httpState=%p\n", fd, data); - - if (httpState) - delete httpState; + delete httpState; } int @@ -389,8 +387,8 @@ HttpStateData::cacheableReply() * 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 @@ -468,9 +466,9 @@ HttpStateData::cacheableReply() */ 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 @?@ */ @@ -645,7 +643,7 @@ HttpStateData::failReply(HttpReply *reply, http_status const & status) entry->replaceHttpReply(reply); if (eof == 1) { - transactionComplete(); + serverComplete(); } } @@ -724,7 +722,8 @@ HttpStateData::processReplyHeader() 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; @@ -757,16 +756,14 @@ HttpStateData::processReplyHeader() 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() { @@ -845,11 +842,11 @@ no_cache: EBIT_SET(entry->flags, ENTRY_REVALIDATE); } - ctx_exit(ctx); #if HEADERS_LOG - headersLog(1, 0, request->method, getReply()); #endif + + ctx_exit(ctx); } HttpStateData::ConnectionStatus @@ -899,41 +896,36 @@ HttpStateData::statusIfComplete() const 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(); } /* @@ -968,7 +960,7 @@ HttpStateData::readReply (size_t len, comm_err_t flag, int xerrno) } if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { - maybeReadData(); + maybeReadVirginBody(); return; } @@ -979,6 +971,7 @@ HttpStateData::readReply (size_t len, comm_err_t flag, int xerrno) if (flag == COMM_OK && len > 0) { readBuf->appended(len); + reply_bytes_read += len; #if DELAY_POOLS DelayId delayId = entry->mem_obj->mostBytesAllowed(); @@ -1011,7 +1004,7 @@ HttpStateData::readReply (size_t len, comm_err_t flag, int xerrno) /* Continue to read... */ /* Timeout NOT increased. This whitespace was from previous reply */ flags.do_next_read = 1; - maybeReadData(); + maybeReadVirginBody(); return; } } @@ -1048,10 +1041,10 @@ HttpStateData::readReply (size_t len, comm_err_t flag, int xerrno) * 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; @@ -1063,14 +1056,14 @@ HttpStateData::readReply (size_t len, comm_err_t flag, int xerrno) 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; @@ -1102,19 +1095,27 @@ HttpStateData::readReply (size_t len, comm_err_t flag, int xerrno) * 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; } @@ -1130,14 +1131,13 @@ HttpStateData::processReplyBody() { if (!flags.headers_parsed) { flags.do_next_read = 1; - maybeReadData(); + maybeReadVirginBody(); return; } #if ICAP_CLIENT if (icapAccessCheckPending) return; - #endif /* @@ -1145,11 +1145,7 @@ HttpStateData::processReplyBody() * 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)) { /* @@ -1194,33 +1190,36 @@ HttpStateData::processReplyBody() 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"); @@ -1228,10 +1227,11 @@ HttpStateData::maybeReadData() 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 @@ -1295,36 +1295,23 @@ HttpStateData::SendComplete(int fd, char *bufnotused, size_t size, comm_err_t er 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; } /* @@ -1732,24 +1719,30 @@ HttpStateData::buildRequestPrefix(HttpRequest * request, } /* 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) { @@ -1788,7 +1781,9 @@ HttpStateData::sendRequest() 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 @@ -1799,12 +1794,15 @@ httpStart(FwdState *fwd) 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, @@ -1813,10 +1811,10 @@ httpStart(FwdState *fwd) } 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) @@ -1825,42 +1823,35 @@ HttpStateData::sendRequestEntityDone() /* 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(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)); @@ -1869,98 +1860,41 @@ HttpStateData::requestBodyHandler(MemBuf &mb) 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(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 @@ -1984,7 +1918,7 @@ HttpStateData::icapAclCheckDone(ICAPServiceRep::Pointer service) { 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 @@ -1995,7 +1929,7 @@ HttpStateData::icapAclCheckDone(ICAPServiceRep::Pointer service) processReplyBody(); if (eof == 1) - transactionComplete(); + serverComplete(); return; } @@ -2009,120 +1943,7 @@ HttpStateData::icapAclCheckDone(ICAPServiceRep::Pointer service) 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 diff --git a/src/http.h b/src/http.h index fcd3c09dc7..8b7bf97b5e 100644 --- a/src/http.h +++ b/src/http.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -38,13 +38,10 @@ #include "comm.h" #include "forward.h" #include "Server.h" -#include "BodyReader.h" #if ICAP_CLIENT #include "ICAP/ICAPServiceRep.h" -class ICAPClientRespmodPrecache; - class ICAPAccessCheck; #endif @@ -56,30 +53,22 @@ public: ~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; @@ -87,13 +76,15 @@ public: 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 @@ -121,12 +112,20 @@ private: 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, @@ -135,8 +134,6 @@ private: static bool decideIfWeDoRanges (HttpRequest * orig_request); #if ICAP_CLIENT - void backstabAdapter(); - void endAdapting(); #endif private: diff --git a/src/structs.h b/src/structs.h index 07bd1f3e0d..16e579b902 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1,6 +1,6 @@ /* - * $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/ @@ -1297,12 +1297,19 @@ unsigned int body_sent: 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: