]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
- ICAP-unrelated improvements from the squid3-icap branch on SF
authorrousskov <>
Fri, 6 Apr 2007 10:50:04 +0000 (10:50 +0000)
committerrousskov <>
Fri, 6 Apr 2007 10:50:04 +0000 (10:50 +0000)
  (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.

46 files changed:
src/HttpMsg.cc
src/HttpMsg.h
src/HttpReply.cc
src/HttpRequest.cc
src/HttpRequest.h
src/ICAP/ICAPClient.cc
src/ICAP/ICAPClient.h
src/ICAP/ICAPClientReqmodPrecache.cc [deleted file]
src/ICAP/ICAPClientReqmodPrecache.h [deleted file]
src/ICAP/ICAPClientRespmodPrecache.cc [deleted file]
src/ICAP/ICAPClientRespmodPrecache.h [deleted file]
src/ICAP/ICAPClientVector.cc [deleted file]
src/ICAP/ICAPClientVector.h [deleted file]
src/ICAP/ICAPConfig.cc
src/ICAP/ICAPConfig.h
src/ICAP/ICAPElements.h
src/ICAP/ICAPInOut.h [moved from src/ICAP/MsgPipeData.h with 67% similarity]
src/ICAP/ICAPInitiator.cc [new file with mode: 0644]
src/ICAP/ICAPInitiator.h [moved from src/ICAP/MsgPipeSink.h with 67% similarity]
src/ICAP/ICAPModXact.cc
src/ICAP/ICAPModXact.h
src/ICAP/ICAPOptXact.cc
src/ICAP/ICAPOptXact.h
src/ICAP/ICAPOptions.cc
src/ICAP/ICAPOptions.h
src/ICAP/ICAPServiceRep.cc
src/ICAP/ICAPServiceRep.h
src/ICAP/ICAPXaction.cc
src/ICAP/ICAPXaction.h
src/ICAP/MsgPipe.cc [deleted file]
src/ICAP/MsgPipe.h [deleted file]
src/ICAP/MsgPipeEnd.h [deleted file]
src/ICAP/MsgPipeSource.h [deleted file]
src/Makefile.am
src/Server.cc
src/Server.h
src/cf.data.pre
src/client_side.cc
src/client_side.h
src/client_side_request.cc
src/client_side_request.h
src/forward.cc
src/ftp.cc
src/http.cc
src/http.h
src/structs.h

index b6fc40324a50444deaea79f3e2d2a451610a90df..52f2f678d1e777f83fca7afcaf04beed3a4c075b 100644 (file)
@@ -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)
index efc6d64e710283833434708c6847b87994df018f..aa1ba0c6be90bbe9797dfc5cf83728de0daa2e43 100644 (file)
@@ -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
index 22f7b64ef6e87b4501b2c2d0b9f142c757c4332a..e641e4c4b23507988a20fdf8320f3d1147a52885 100644 (file)
@@ -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();
index cd766587cfcaa6acbf6e34d16244bff047b255a1..a68e28c245ba9c7c7b2adac81a07d215ad587aca 100644 (file)
@@ -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)
index 9fd3e6c3d3c51ab9d5c6123984f3e4bec187d734..cffb1f90edff2b05af38274cfe751c420021ed1e 100644 (file)
@@ -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;
index 3f1504b3fd903a13ec21e00961863cda255086dd..df2fd4966ad0751428cd579c2154ce39bbe0b588 100644 (file)
@@ -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());
 }
index cb4c1e4647020b95d76a1a3d6678651fb14a304d..daed777c8670a7fd7048c0dd03ffb12e62b34349 100644 (file)
@@ -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/
 #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 (file)
index b66e34c..0000000
+++ /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<HttpRequest*>(adapted->data->header);
-
-    if (req && req->content_length > 0) {
-        assert(req->body_reader == NULL);
-        req->body_reader = new BodyReader(req->content_length, readBody, abortBody, kickBody, this);
-    }
-
-    http->takeAdaptedHeaders(adapted->data->header);
-    noteSourceProgress(p);
-}
-
-/*
- * This is where we receive a notification from the other
- * side of the MsgPipe that new adapted data is available.
- * We, in turn, tell whoever is reading from the request's
- * body_reader about the new data.
- */
-void ICAPClientReqmodPrecache::noteSourceProgress(MsgPipe *p)
-{
-    debug(93,3)("ICAPClientReqmodPrecache::noteSourceProgress() called\n");
-    //tell ClientHttpRequest to store a fresh portion of the adapted response
-
-    if (p->data->body->hasContent()) {
-        /*
-         * NOTE: req will be NULL if this is a "request satisfaction"
-         * ICAP reply.  In other words, the ICAP REQMOD reply may
-         * contain an HTTP response, in which case we'll have a body, but
-         * adapted->data->header will be an HttpReply, not an HttpRequest.
-         */
-        HttpRequest *req = dynamic_cast<HttpRequest*>(adapted->data->header);
-
-        if (req) {
-            debugs(93,3,HERE << "notifying body_reader, contentSize() = " << p->data->body->contentSize());
-            req->body_reader->notify(p->data->body->contentSize());
-        } else {
-            http->takeAdaptedBody(adapted->data->body);
-        }
-    }
-}
-
-void ICAPClientReqmodPrecache::tellDoneAdapting()
-{
-    debug(93,3)("ICAPClientReqmodPrecache::tellDoneAdapting() called\n");
-    //tell ClientHttpRequest that we expect no more response data
-    http->doneAdapting(); // does not delete us (yet?)
-    stop(notifyNone);
-    // we should be eventually deleted by owner in ~ClientHttpRequest()
-}
-
-void ICAPClientReqmodPrecache::tellAbortAdapting()
-{
-    debug(93,3)("ICAPClientReqmodPrecache::tellAbortAdapting() called\n");
-    // tell ClientHttpRequest that we are aborting ICAP processing prematurely
-    http->abortAdapting();
-}
-
-// internal cleanup
-void ICAPClientReqmodPrecache::stop(Notify notify)
-{
-    /*
-     * NOTE: We do not clean up "adapted->sink" here because it may
-     * have an HTTP message body that needs to stay around a little
-     * while longer so that the HTTP server-side can forward it on.
-     */
-
-    // XXX: who will clean up the "adapted->sink" then? Does it happen
-    // when the owner deletes us? Is that why we are deleted when the
-    // owner is destroyed and not when ICAP adaptation is done, like
-    // in http.cc case?
-
-    // XXX: "adapted->sink" does not really have an "HTTP message body",
-    // In fact, it simply points to "this". Should the above comment
-    // refer to adapted and adapted->data->body?
-
-    ICAPClientVector::clean(notify, false);
-}
-
-/*
- * Something that needs to read the adapated request body
- * calls this function, via the BodyReader class.  We copy
- * the body data from our bodybuf object to the BodyReader
- * MemBuf, which was passed as a reference to this function.
- */
-size_t
-ICAPClientReqmodPrecache::readBody(void *data, MemBuf &mb, size_t size)
-{
-    ICAPClientReqmodPrecache *icap = static_cast<ICAPClientReqmodPrecache *>(data);
-    assert(icap != NULL);
-    assert(icap->adapted != NULL);
-    assert(icap->adapted->data != NULL);
-    MemBuf *bodybuf = icap->adapted->data->body;
-    assert(bodybuf != NULL);
-    debugs(93,3,HERE << "readBody requested size " << size);
-    debugs(93,3,HERE << "readBody bodybuf size " << bodybuf->contentSize());
-
-    if ((mb_size_t) size > bodybuf->contentSize())
-        size = bodybuf->contentSize();
-
-    debugs(93,3,HERE << "readBody actual size " << size);
-
-    assert(size);
-
-    mb.append(bodybuf->content(), size);
-
-    bodybuf->consume(size);
-
-    return size;
-}
-
-void
-ICAPClientReqmodPrecache::abortBody(void *data, size_t remaining)
-{
-    if (remaining >= 0) {
-        debugs(93,1,HERE << "ICAPClientReqmodPrecache::abortBody size " << remaining);
-        // more?
-    }
-
-    ICAPClientReqmodPrecache *icap = static_cast<ICAPClientReqmodPrecache *>(data);
-    icap->stop(notifyIcap);
-}
-
-/*
- * Restart reading the adapted response from the ICAP server in case
- * the body buffer became full and we stopped reading.
- */
-void
-ICAPClientReqmodPrecache::kickBody(void *data)
-{
-    debugs(93,3,HERE << "ICAPClientReqmodPrecache::kickBody");
-    ICAPClientReqmodPrecache *icap = static_cast<ICAPClientReqmodPrecache *>(data);
-    assert(icap->adapted != NULL);
-    icap->adapted->sendSinkNeed();
-}
diff --git a/src/ICAP/ICAPClientReqmodPrecache.h b/src/ICAP/ICAPClientReqmodPrecache.h
deleted file mode 100644 (file)
index d0285a5..0000000
+++ /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 (file)
index 7df4d67..0000000
+++ /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<HttpReply*>(adapted->data->header);
-    /*
-     * The ICAP reply MUST have a new HTTP reply header, or else
-     * it is an invalid ICAP message.  Invalid ICAP messages should
-     * be handled prior to this point.
-     */
-    assert(reply); // check that ICAP xaction created the right object
-    assert(reply == adapted->data->header);
-
-    /*
-     * Examine the HTTP reply headers to find out if there is an associated
-     * body.  We should probably check the ICAP Encapsulated header values
-     * as well.
-     */
-    ssize_t dummy;
-    bool expect_body = reply->expectingBody(virgin->data->cause->method, dummy);
-
-    if (!serverState->takeAdaptedHeaders(reply)) // deletes us
-        return;
-
-    if (expect_body)
-        noteSourceProgress(p);
-    else
-        noteSourceFinish(p);
-}
-
-// ICAP client sends more data
-void ICAPClientRespmodPrecache::noteSourceProgress(MsgPipe *p)
-{
-    debug(93,3)("ICAPClientRespmodPrecache::noteSourceProgress() called\n");
-    //tell ServerStateData to store a fresh portion of the adapted response
-
-    assert(serverState);
-
-    if (p->data->body->hasContent()) {
-        if (!serverState->takeAdaptedBody(p->data->body))
-            return;
-
-        // HttpStateData::takeAdaptedBody does not detect when we have enough,
-        // so we always notify source that there more buffer space is available
-        if (p->data->body->hasPotentialSpace())
-            adapted->sendSinkNeed(); 
-    }
-}
-
-void
-ICAPClientRespmodPrecache::tellSpaceAvailable()
-{
-    serverState->icapSpaceAvailable();
-}
-
-void
-ICAPClientRespmodPrecache::tellDoneAdapting()
-{
-    serverState->finishAdapting(); // deletes us
-}
-
-void
-ICAPClientRespmodPrecache::tellAbortAdapting()
-{
-    debug(93,3)("ICAPClientReqmodPrecache::tellAbortAdapting() called\n");
-    // tell ClientHttpRequest that we are aborting ICAP processing prematurely
-    serverState->abortAdapting(); // deletes us
-}
-
diff --git a/src/ICAP/ICAPClientRespmodPrecache.h b/src/ICAP/ICAPClientRespmodPrecache.h
deleted file mode 100644 (file)
index db4e10d..0000000
+++ /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 (file)
index 16e34bf..0000000
+++ /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 (file)
index 462a53e..0000000
+++ /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 */
index d0480c04482390a96d40fb7eb537012fba399635..e4fd192bd0ea7111a77f3900cf667983e23b86c5 100644 (file)
@@ -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<ICAPServiceRep::Pointer>::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 <<
index 34a11d4b8ff21ed8767ce68ede6e06afa51e6aa2..391b1995381a801aa38b6e9b3930f6a40fc9d8b3 100644 (file)
@@ -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<ICAPServiceRep::Pointer> services;
     Vector<ICAPClass*> classes;
@@ -126,4 +128,6 @@ public:
 
 };
 
+extern ICAPConfig TheICAPConfig;
+
 #endif /* SQUID_ICAPCONFIG_H */
index 066f9e8c5859ef15f37247c06c06aeedfdb8f084..75e18ac42765b13b02a1532c36dcc28edecf5835 100644 (file)
@@ -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);
similarity index 67%
rename from src/ICAP/MsgPipeData.h
rename to src/ICAP/ICAPInOut.h
index d30beb5c155849d6dd7e7940d49b6ad08a673cb2..f3fe51444f181f9ea12aa2a22d4f248071de66d0 100644 (file)
@@ -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/
  *
  */
 
-#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 (file)
index 0000000..1938704
--- /dev/null
@@ -0,0 +1 @@
+// XXX: remove me!
similarity index 67%
rename from src/ICAP/MsgPipeSink.h
rename to src/ICAP/ICAPInitiator.h
index f5c65cfaab2bbb88e5869f672bfc947dea83273b..365c0530189d6783d18709edede7f589895cf8b3 100644 (file)
@@ -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/
  *
  */
 
-#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 */
index 253d60da254c4b7c11cea164e81006f5b1f76d33..a2566b4ae03aa2f618cf1a5eb4ec213095b6a2ff 100644 (file)
@@ -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"
 //     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<size_t>(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<size_t>(buf.contentSize());
+    if (!virgin.body_pipe)
+        return;
+
+    BodyPipe &bp = *virgin.body_pipe;
+    const size_t have = static_cast<size_t>(bp.buf().contentSize());
     const size_t end = virginConsumed + have;
     size_t offset = end;
 
-    if (virginWriteClaim.active())
-        offset = XMIN(virginWriteClaim.offset(), offset);
+    if (virginBodyWriting.active())
+        offset = XMIN(virginBodyWriting.offset(), offset);
 
-    if (virginSendClaim.active())
-        offset = XMIN(virginSendClaim.offset(), offset);
+    if (virginBodySending.active())
+        offset = XMIN(virginBodySending.offset(), offset);
 
     Must(virginConsumed <= offset && offset <= end);
 
     if (const size_t size = offset - virginConsumed) {
         debugs(93, 8, HERE << "consuming " << size << " out of " << have <<
                " virgin body bytes");
-        buf.consume(size);
+        bp.consume(size);
         virginConsumed += size;
-
-        if (!state.doneReceiving)
-            virgin->sendSinkNeed();
     }
 }
 
@@ -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<size_t>(to.potentialSpaceSize()),
-                             sizeMax);
-    debugs(93, 5, "ICAPModXact echos " << size << " out of " << sizeMax <<
+    Must(adapted.body_pipe != NULL);
+    Must(virginBodySending.active());
+
+    const size_t sizeMax = virginContentSize(virginBodySending);
+    debugs(93,5, HERE << "will echo up to " << sizeMax << " bytes from " <<
+        virgin.body_pipe->status());
+    debugs(93,5, HERE << "will echo up to " << sizeMax << " bytes to   " <<
+        adapted.body_pipe->status());
+
+    if (sizeMax > 0) {
+        const size_t size = adapted.body_pipe->putMoreData(virginContentData(virginBodySending), sizeMax);
+        debugs(93,5, HERE << "echoed " << size << " out of " << sizeMax <<
            " bytes");
-
-    if (size > 0) {
-        to.append(claimContent(virginSendClaim), size);
-        virginSendClaim.release(size);
+        virginBodySending.progress(size);
         virginConsume();
-        adapted->sendSourceProgress();
     }
 
-    if (state.doneReceiving && claimSize(virginSendClaim) <= 0) {
-        debugs(93, 5, "ICAPModXact echoed all " << status());
+    if (virginBodyEndReached(virginBodySending)) {
+        debugs(93, 5, "ICAPModXact echoed all" << status());
         stopSending(true);
     } else {
-        debugs(93, 5, "ICAPModXact has " << from.contentSize() << " bytes " <<
-               "and expects more to echo " << status());
-        virgin->sendSinkNeed(); // TODO: timeout if sink is broken
+        debugs(93, 5, "ICAPModXact has " <<
+            virgin.body_pipe->buf().contentSize() << " bytes " <<
+            "and expects more to echo" << status());
+        // TODO: timeout if virgin or adapted pipes are broken
     }
 }
 
 bool ICAPModXact::doneSending() const
 {
-    Must((state.sending == State::sendingDone) == (!adapted));
     return state.sending == State::sendingDone;
 }
 
@@ -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<const HttpRequest*>(oldHead)) {
         HttpRequest *newR = new HttpRequest;
-        newR->client_addr = oldR->client_addr;
+        inheritVirginProperties(*newR, *oldR);
         newHead = newR;
     } else
     if (dynamic_cast<const HttpReply*>(oldHead))
         newHead = new HttpReply;
     Must(newHead);
 
-    adapted->data->setHeader(newHead);
+    adapted.setHeader(newHead);
 
     // parse the buffer back
     http_status error = HTTP_STATUS_NONE;
@@ -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<HttpRequest*>(adapted.header)) {
+            const HttpRequest *oldR = dynamic_cast<const HttpRequest*>(virgin.header);
+            Must(oldR);
+            // TODO: the adapted request did not really originate from the 
+            // client; give proxy admin an option to prevent copying of 
+            // sensitive client information here. See the following thread:
+            // http://www.squid-cache.org/mail-archive/squid-dev/200703/0040.html
+            inheritVirginProperties(*newHead, *oldR);
+        }
     }
 
-    state.parsing = State::psBody;
+    decideOnParsingBody();
 }
 
 // parses both HTTP and ICAP headers
@@ -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<const HttpRequest*>(virgin->data->header);
+    const HttpRequest *request = virgin.cause ?
+        virgin.cause :
+        dynamic_cast<const HttpRequest*>(virgin.header);
 
-    // to simplify, we could we assume that request is always available
+    // to simplify, we could assume that request is always available
 
     String urlPath;
     if (request) {
@@ -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<HttpRequest*>(virgin->data->
-                               header))
-            method = req->method;
-        else
-            return;
+    if (HttpRequest *req = dynamic_cast<HttpRequest*>(msg))
+        method = req->method;
+    else
+        method = METHOD_NONE;
 
     ssize_t size;
-    if (virgin->data->header->expectingBody(method, size)) {
-        virginBody.expect(size)
-        ;
-        debugs(93, 6, "ICAPModXact expects virgin body; size: " << size);
+    // expectingBody returns true for zero-sized bodies, but we will not
+    // get a pipe for that body, so we treat the message as bodyless
+    if (method != METHOD_NONE && msg->expectingBody(method, size) && size) {
+        debugs(93, 6, "ICAPModXact expects virgin body from " << 
+            virgin.body_pipe << "; size: " << size);
+
+        virginBody.expect(size);
+        virginBodyWriting.plan();
+
+        // sign up as a body consumer
+        Must(msg->body_pipe != NULL);
+        Must(msg->body_pipe == virgin.body_pipe);
+        Must(virgin.body_pipe->setConsumerIfNotLate(this));
+
+        // make sure TheBackupLimit is in-sync with the buffer size
+        Must(TheBackupLimit <= static_cast<size_t>(msg->body_pipe->buf().max_capacity));
     } else {
         debugs(93, 6, "ICAPModXact does not expect virgin body");
+        Must(msg->body_pipe == NULL);
+        checkConsuming();
     }
 }
 
+void ICAPModXact::makeAdaptedBodyPipe(const char *what) {
+    Must(!adapted.body_pipe);
+    Must(!adapted.header->body_pipe);
+    adapted.header->body_pipe = new BodyPipe(this);
+    adapted.body_pipe = adapted.header->body_pipe;
+    debugs(93, 7, HERE << "will supply " << what << " via " <<
+        adapted.body_pipe << " pipe");
+}
+
 
 // TODO: Move SizedEstimate, MemBufBackup, and ICAPPreview elsewhere
 
@@ -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<ssize_t>(aGoal) :
-              XMIN(static_cast<ssize_t>(aGoal), theGoal);
 }
 
-void MemBufClaim::disable()
+void VirginBodyAct::disable()
 {
-    theStart = -1;
+    theStart = -2;
 }
 
-void MemBufClaim::release(size_t size)
+void VirginBodyAct::progress(size_t size)
 {
     Must(active());
     Must(size >= 0);
     theStart += static_cast<ssize_t>(size);
-
-    if (limited() && theStart >= theGoal)
-        disable();
 }
 
-size_t MemBufClaim::offset() const
+size_t VirginBodyAct::offset() const
 {
     Must(active());
     return static_cast<size_t>(theStart);
 }
 
-bool MemBufClaim::limited() const
-{
-    Must(active());
-    return theGoal >= 0;
-}
-
 
 ICAPPreview::ICAPPreview(): theWritten(0), theAd(0), theState(stDisabled)
 {}
@@ -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;
 }
index 35867fd475f5a7daa7c70d21118dda1ac06b8a9f..2ea49d222f69015e66c207b5bab8e5d51f371631 100644 (file)
@@ -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/
 #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<ICAPModXact> Pointer;
 
 public:
-    ICAPModXact();
+    ICAPModXact(ICAPInitiator *anInitiator, HttpMsg *virginHeader, HttpRequest *virginCause, ICAPServiceRep::Pointer &s);
 
-    // called by ICAPClient
-    void init(ICAPServiceRep::Pointer&, MsgPipe::Pointer &aVirgin, MsgPipe::Pointer &anAdapted, Pointer &aSelf);
+    // communication with the initiator
+    void noteInitiatorAborted();
+    AsyncCallWrapper(93,3, ICAPModXact, noteInitiatorAborted)
 
-    // pipe source methods; called by Anchor while receiving the adapted msg
-    virtual void noteSinkNeed(MsgPipe *p);
-    virtual void noteSinkAbort(MsgPipe *p);
+    // BodyProducer methods
+    virtual void noteMoreBodySpaceAvailable(BodyPipe &);
+    virtual void noteBodyConsumerAborted(BodyPipe &);
 
-    // pipe sink methods; called by ICAP while sending the virgin message
-    virtual void noteSourceStart(MsgPipe *p);
-    virtual void noteSourceProgress(MsgPipe *p);
-    virtual void noteSourceFinish(MsgPipe *p);
-    virtual void noteSourceAbort(MsgPipe *p);
+    // BodyConsumer methods
+    virtual void noteMoreBodyDataAvailable(BodyPipe &);
+    virtual void noteBodyProductionEnded(BodyPipe &);
+    virtual void noteBodyProducerAborted(BodyPipe &);
 
     // comm handlers
     virtual void handleCommConnected();
@@ -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; }
 
index 911d3c840bf7e7dfd5def65ffe1e8f3da4180230..2353c410438184d1d5a03fa0420f58e243d33f76 100644 (file)
 
 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());
 }
index 8fce3de9a65016f5b0a1c95a19fb44d756895a8b..45f3e6f7ed789d8d305110caf8558bfe228dc1cf 100644 (file)
@@ -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 */
index 41cffef5876bb94df3118cf54d1f472d2d30b8c7..c28abaec70e2eb68a1d75653d5549e5b0996a6b2 100644 (file)
@@ -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)
index 58c3f9a632ed746921d5f370b467911a472b3f1e..d18c81ed8d41b166451f38209af9d6769df4c606 100644 (file)
@@ -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;
 
index 6c9c6ff630d85e46984d3749f252e5b8546f6b83..ab38ba94d4f23663d122abab53fc51da020ced47 100644 (file)
@@ -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<ICAPServiceRep*>(data);
     Must(service);
-    service->noteNewOptions(x);
+    service->noteNewOptions(newOptions);
 }
 
-void ICAPServiceRep::noteNewOptions(ICAPOptXact *x)
+void ICAPServiceRep::noteNewOptions(ICAPOptions *newOptions)
 {
-    Must(x);
+    // newOptions may be NULL
+
     Must(waiting);
     waiting = false;
 
-    changeOptions(x->options);
-    x->options = NULL;
-    delete x;
+    changeOptions(newOptions);
 
     debugs(93,3, "ICAPService got new options and is now " << status());
 
@@ -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();
index 4f9af4a1c861907b1a0af3e057c9b22b9f5a2f7a..a1cd3086274b977e4bbb41ab384419f87cd8e542 100644 (file)
@@ -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
index c87aeaad7d494bd0f7c61580a08133275eabad1c..359e845de3560641bf91a9b14f901eab5ae840e6 100644 (file)
@@ -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<size_t>(readBuf.potentialSpaceSize()) <= commBufSize);
+    debugs(93,3, typeName << " constructed, this=" << this <<
+        " [icapx" << id << ']'); // we should not call virtual status() here
 }
 
 ICAPXaction::~ICAPXaction()
 {
-    debug(93,3)("%s destructing, this=%p\n", typeName, this);
-    doStop();
-    readBuf.clean();
-    memFreeBuf(commBufSize, commBuf);
+    debugs(93,3, typeName << " destructed, this=" << this <<
+        " [icapx" << id << ']'); // we should not call virtual status() here
+}
+
+void ICAPXaction::start()
+{
+    debugs(93,3, HERE << typeName << " starts" << status());
+
+    Must(self != NULL); // set by AsyncStart;
+
+    readBuf.init(SQUID_TCP_SO_RCVBUF, SQUID_TCP_SO_RCVBUF);
+    commBuf = (char*)memAllocBuf(SQUID_TCP_SO_RCVBUF, &commBufSize);
+    // make sure maximum readBuf space does not exceed commBuf size
+    Must(static_cast<size_t>(readBuf.potentialSpaceSize()) <= commBufSize);
 }
 
 // TODO: obey service-specific, OPTIONS-reported connection limit
@@ -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);
     }
 }
 
index b4fac40b87b1d7696652e231bbfcecd62964477b..c4a7c8f7987a171b57f4f9bb7eb43bbed932aff6 100644 (file)
@@ -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/
 #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<ICAPXaction> Pointer;
 
+    // Use this to start ICAP transactions because they need a pointer
+    // to self and because the start routine may result in failures/callbacks.
+    static ICAPXaction *AsyncStart(ICAPXaction *x);
+
 public:
     ICAPXaction(const char *aTypeName);
     virtual ~ICAPXaction();
@@ -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 (file)
index 83f237c..0000000
+++ /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<MsgPipe*>(p); \
-    if (pipe && pipe->canSend(pipe->destination, #callName, false)) \
-        pipe->destination->note##callName(pipe); \
-}
-
-// static event callbacks
-MsgPipe_MAKE_CALLBACK(SourceStart, sink)
-MsgPipe_MAKE_CALLBACK(SourceProgress, sink)
-MsgPipe_MAKE_CALLBACK(SourceFinish, sink)
-MsgPipe_MAKE_CALLBACK(SourceAbort, sink)
-MsgPipe_MAKE_CALLBACK(SinkNeed, source)
-MsgPipe_MAKE_CALLBACK(SinkAbort, source)
-
-
-MsgPipe::MsgPipe(const char *aName): name(aName),
-        data(NULL), source(NULL), sink(NULL)
-{}
-
-MsgPipe::~MsgPipe()
-{
-    delete data;
-    assert(source == NULL);
-    assert(sink == NULL);
-};
-
-void MsgPipe::sendSourceStart()
-{
-    debug(99,5)("MsgPipe::sendSourceStart() called\n");
-    sendLater("SourceStart", &MsgPipe_sendSourceStart, sink);
-}
-
-
-
-void MsgPipe::sendSourceProgress()
-{
-    debug(99,5)("MsgPipe::sendSourceProgress() called\n");
-    sendLater("SourceProgress", &MsgPipe_sendSourceProgress, sink);
-}
-
-void MsgPipe::sendSourceFinish()
-{
-    debug(99,5)("MsgPipe::sendSourceFinish() called\n");
-    sendLater("sendSourceFinish", &MsgPipe_sendSourceFinish, sink);
-    source = NULL;
-}
-
-void MsgPipe::sendSourceAbort()
-{
-    debug(99,5)("MsgPipe::sendSourceAbort() called\n");
-    sendLater("SourceAbort", &MsgPipe_sendSourceAbort, sink);
-    source = NULL;
-}
-
-
-void MsgPipe::sendSinkNeed()
-{
-    debug(99,5)("MsgPipe::sendSinkNeed() called\n");
-    sendLater("SinkNeed", &MsgPipe_sendSinkNeed, source);
-}
-
-void MsgPipe::sendSinkAbort()
-{
-    debug(99,5)("MsgPipe::sendSinkAbort() called\n");
-    sendLater("SinkAbort", &MsgPipe_sendSinkAbort, source);
-    sink = NULL;
-}
-
-void MsgPipe::sendLater(const char *callName, EVH * handler, MsgPipeEnd *destination)
-{
-    if (canSend(destination, callName, true))
-        eventAdd(callName, handler, this, 0.0, 0, true);
-}
-
-bool MsgPipe::canSend(MsgPipeEnd *destination, const char *callName, bool future)
-{
-    const bool res = destination != NULL;
-    const char *verb = future ?
-                       (res ? "will send " : "wont send ") :
-                               (res ? "sends " : "ignores ");
-    debugs(93,5, "MsgPipe " << name << "(" << this << ") " <<
-           verb << callName << " to the " <<
-           (destination ? destination->kind() : "destination") << "(" <<
-           destination << "); " <<
-           "data: " << data << "; source: " << source << "; sink " << sink);
-    return res;
-}
diff --git a/src/ICAP/MsgPipe.h b/src/ICAP/MsgPipe.h
deleted file mode 100644 (file)
index 7fd5ac8..0000000
+++ /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<MsgPipe> Pointer;
-
-    MsgPipe(const char *aName = "anonym");
-    ~MsgPipe();
-
-    // the pipe source calls these to notify the sink
-    void sendSourceStart();
-    void sendSourceProgress();
-    void sendSourceFinish();
-    void sendSourceAbort();
-
-    // the pipe sink calls these to notify the source
-    void sendSinkNeed();
-    void sendSinkAbort();
-
-    // private method exposed for the event handler only
-    bool canSend(MsgPipeEnd *destination, const char *callName, bool future);
-
-public:
-    const char *name; // unmanaged pointer used for debugging only
-
-    MsgPipeData *data;
-    MsgPipeSource *source;
-    MsgPipeSink *sink;
-
-private:
-    void sendLater(const char *callName, EVH * handler, MsgPipeEnd *destination);
-
-    CBDATA_CLASS2(MsgPipe);
-};
-
-#endif /* SQUID_MSGPIPE_H */
diff --git a/src/ICAP/MsgPipeEnd.h b/src/ICAP/MsgPipeEnd.h
deleted file mode 100644 (file)
index 4fb74cc..0000000
+++ /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 (file)
index 97a7d6f..0000000
+++ /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 */
index 5336b586eec1ad2956dc31ebb4051ad3af8e2cb3..4f7e533b7c9423cdd02f94a1873177b58391d262 100644 (file)
@@ -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 \
index 2566208a9d1bef180cba1c5652468e4d794fcf64..2a390adcf0dfd86dd26ed631d52a5632274c0e8c 100644 (file)
@@ -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
 #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<ServerStateData *>(data);
+    server->sentRequestBody(fd, size, errflag);
+}
+
+// called when we wrote request headers(!) or a part of the body
+void
+ServerStateData::sentRequestBody(int fd, size_t size, comm_err_t errflag)
+{
+    debug(11, 5) ("sentRequestBody: FD %d: size %d: errflag %d.\n",
+                  fd, (int) size, errflag);
+    debugs(32,3,HERE << "sentRequestBody called");
+
+    requestSender = NULL;
+
+    if (size > 0) {
+        fd_bytes(fd, size, FD_WRITE);
+        kb_incr(&statCounter.server.all.kbytes_out, size);
+        // kids should increment their counters
+    }
+
+    if (errflag == COMM_ERR_CLOSING)
+        return;
+
+    if (!requestBodySource) {
+        debugs(9,3, HERE << "detected while-we-were-sending abort");
+        return; // do nothing;
+    }
+
+    if (errflag) {
+        debug(11, 1) ("sentRequestBody error: FD %d: %s\n", fd, xstrerr(errno));
+        ErrorState *err;
+        err = errorCon(ERR_WRITE_ERROR, HTTP_BAD_GATEWAY, fwd->request);
+        err->xerrno = errno;
+        fwd->fail(err);
+        abortTransaction("I/O error while sending request body");
+        return;
+    }
+
+    if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+        abortTransaction("store entry aborted while sending request body");
+        return;
+    }
+
+    if (requestBodySource->exhausted())
+        doneSendingRequestBody();
+    else
+        sendMoreRequestBody();
 }
 
+void
+ServerStateData::sendMoreRequestBody()
+{
+    assert(requestBodySource != NULL);
+    assert(!requestSender);
+    MemBuf buf;
+    if (requestBodySource->getMoreData(buf)) {
+        debugs(9,3, HERE << "will write " << buf.contentSize() << " request body bytes");
+        requestSender = &ServerStateData::sentRequestBodyWrapper;
+        comm_write_mbuf(dataDescriptor(), &buf, requestSender, this);
+    } else {
+        debugs(9,3, HERE << "will wait for more request body bytes or eof");
+        requestSender = NULL;
+    }
+}
+
+// called by noteIcapHeadersAdapted(), HTTP server overwrites this
+void
+ServerStateData::haveParsedReplyHeaders()
+{
+    // default does nothing
+}
+
+
 #if ICAP_CLIENT
 /*
  * Initiate an ICAP transaction.  Return true on success.
@@ -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<HttpReply*>(adaptedHeadSource->adapted.header);
+    HTTPMSGLOCK(rep);
+    adaptedHeadSource = NULL; // we do not expect any more messages from it
+
+    if (abortOnBadEntry("entry went bad while waiting for adapted headers")) {
+        HTTPMSGUNLOCK(rep); // hopefully still safe, even if "this" is deleted
+        return;
+    }
+
+    assert(rep);
+    entry->replaceHttpReply(rep);
+    HTTPMSGUNLOCK(reply);
+
+    reply = rep; // already HTTPMSGLOCKed above
+
+    haveParsedReplyHeaders();
+
+    assert(!adaptedBodySource);
+    if (reply->body_pipe != NULL) {
+        // subscribe to receive adapted body
+        adaptedBodySource = reply->body_pipe;
+        // assume that ICAP does not auto-consume on failures
+        assert(adaptedBodySource->setConsumerIfNotLate(this));
+    } else {
+        // no body
+        handleIcapCompleted();
+    }
+
+}
+
+// will not receive adapted response headers (and, hence, body)
+void
+ServerStateData::noteIcapHeadersAborted()
+{
+    adaptedHeadSource = NULL;
+    handleIcapAborted();
+}
+
+// more adapted response body is available
+void
+ServerStateData::handleMoreAdaptedBodyAvailable()
+{
+    const size_t contentSize = adaptedBodySource->buf().contentSize();
+
+    debugs(11,5, HERE << "consuming " << contentSize << " bytes of adapted " <<
+           "response body at offset " << adaptedBodySource->consumedSize());
+
+    if (abortOnBadEntry("entry refuses adapted body"))
+        return;
+
+    assert(entry);
+    BodyPipeCheckout bpc(*adaptedBodySource);
+    const StoreIOBuffer ioBuf(&bpc.buf, bpc.offset);
+    entry->write(ioBuf);
+    bpc.buf.consume(contentSize);
+    bpc.checkIn();
+}
+
+// the entire adapted response body was produced, successfully
+void
+ServerStateData::handleAdaptedBodyProductionEnded()
+{
+    stopConsumingFrom(adaptedBodySource);
+
+    if (abortOnBadEntry("entry went bad while waiting for adapted body eof"))
+        return;
+
+    handleIcapCompleted();
+}
+
+// premature end of the adapted response body
+void ServerStateData::handleAdaptedBodyProducerAborted()
+{
+    stopConsumingFrom(adaptedBodySource);
+    handleIcapAborted();
+}
+
+// common part of noteIcapHeadersAdapted and handleAdaptedBodyProductionEnded
+void
+ServerStateData::handleIcapCompleted()
+{
+    debugs(11,5, HERE << "handleIcapCompleted");
+    cleanIcap();
+    completeForwarding();
+    quitIfAllDone();
+}
+
+// common part of noteIcap*Aborted and noteBodyConsumerAborted methods
+void
+ServerStateData::handleIcapAborted()
+{
+    debugs(11,5, HERE << "handleIcapAborted; entry empty: " << entry->isEmpty());
+
+    if (abortOnBadEntry("entry went bad while ICAP aborted"))
+        return;
+
+    if (entry->isEmpty()) {
+        debugs(11,9, HERE << "creating ICAP error entry after ICAP failure");
+        ErrorState *err =
+            errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
+        err->xerrno = errno;
+        fwd->fail(err);
+        fwd->dontRetry(true);
+    }
+
+    debugs(11,5, HERE << "bailing after ICAP failure");
+
+    cleanIcap();
+    closeServer();
+    quitIfAllDone();
+}
+
 #endif
index 5fb82c160dfde54c27672a39449914afb8fc37ad..c8f03745e59001c1184edf225f4b70f3b6db36df 100644 (file)
@@ -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
  *
  */
 
 /*
- * 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 */
index 33e851013fb679ea898bf11aa6a0ad4843fe3a81..d4ba54fd66a95a095269eec4bbcebf2ea4dbfc68 100644 (file)
@@ -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
index 0b533cd1d6e2c1c0cf9bf76f8e646bf9a061b95f..6176c8f99c43ee25fa4b741ca88e517e86e86979 100644 (file)
@@ -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 *
index b94735c8028fdf615e54a3259e49cc5a22dea228..ad79fe414eef0b90ff17cc7a19f0c3c5969cd74e 100644 (file)
@@ -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 */
index 2ec0718ab062175fd07a091c57c3b8b8396e471c..5242f30d46f3780a19b3c9c2ffd576899efe3ac8 100644 (file)
@@ -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<ClientHttpRequest*>(data);
-    chr->icapSendRequestBody(mb);
-}
-
-
-/*
- * icapSendRequestBody
- *
- * Sends some chunk of a request body to the ICAP side.  Must make sure
- * that the ICAP-side can accept the data we have.  If there is more
- * body data to read, then schedule another BodyReader callback.
- */
-void
-ClientHttpRequest::icapSendRequestBody(MemBuf &mb)
+bool
+ClientHttpRequest::startIcap(ICAPServiceRep::Pointer service)
 {
-    ssize_t size_to_send  = mb.contentSize();
-    debugs(32,3,HERE << "have " << mb.contentSize() << " bytes in mb");
-
-    if (size_to_send == 0) {
-        /*
-         * An error occurred during this transaction.  Tell ICAP that we're done.
-         */
-
-        if (icap)
-            icap->doneSending();
-
-        return;
-    }
-
-    debugs(32,3,HERE << "icap->potentialSpaceSize() = " << icap->potentialSpaceSize());
-
-    if (size_to_send > icap->potentialSpaceSize())
-        size_to_send = icap->potentialSpaceSize();
-
-    if (size_to_send) {
-        debugs(32,3,HERE << "sending " << size_to_send << " body bytes to ICAP");
-        StoreIOBuffer sbuf(size_to_send, 0, mb.content());
-        icap->sendMoreData(sbuf);
-        icap->body_reader->consume(size_to_send);
-        icap->body_reader->bytes_read += size_to_send;
-        debugs(32,3," HTTP client body bytes_read=" << icap->body_reader->bytes_read);
-    } else {
-        debugs(32,2,HERE << "cannot send body data to ICAP");
-        debugs(32,2,HERE << "\tBodyReader MemBuf has " << mb.contentSize());
-        debugs(32,2,HERE << "\tbut icap->potentialSpaceSize() is " << icap->potentialSpaceSize());
-        return;
-    }
-
-    /*
-     * If we sent some data this time, and there is more data to
-     * read, then schedule another read request via BodyReader.
-     */
-    if (size_to_send && icap->body_reader->remaining()) {
-        debugs(32,3,HERE << "calling body_reader->read()");
-        icap->body_reader->read(icapSendRequestBodyWrapper, this);
-    } else {
-        debugs(32,3,HERE << "No more request body bytes to send");
-        icap->doneSending();
+    debugs(85, 3, HERE << this << " ClientHttpRequest::startIcap() called");
+    if (!service) {
+        debug(85,3)("ClientHttpRequest::startIcap fails: lack of service\n");
+        return false;
     }
-}
-
-/*
- * Called by ICAPAnchor when it has space available for us.
- */
-void
-ClientHttpRequest::icapSpaceAvailable()
-{
-    debugs(85,3,HERE << this << " ClientHttpRequest::icapSpaceAvailable() called\n");
-
-    if (request->body_reader != NULL && icap->body_reader == NULL) {
-        debugs(32,3,HERE << "reassigning HttpRequest->body_reader to ICAP");
-        /*
-         * ICAP hooks on to the BodyReader that gets data from
-         * ConnStateData.  We'll make a new BodyReader that
-         * HttpStateData can use if the adapted response has a
-         * request body.  See ICAPClientReqmodPrecache::noteSourceStart()
-         */
-        icap->body_reader = request->body_reader;
-        request->body_reader = NULL;
+    if (service->broken()) {
+        debug(85,3)("ClientHttpRequest::startIcap fails: broken service\n");
+        return false;
     }
 
-    if (icap->body_reader == NULL)
-        return;
-
-    if (icap->body_reader->callbackPending())
-        return;
-
-    debugs(32,3,HERE << "Calling read() for body data");
-
-    icap->body_reader->read(icapSendRequestBodyWrapper, this);
+    assert(!icapHeadSource);
+    assert(!icapBodySource);
+    icapHeadSource = new ICAPModXact(this, request, NULL, service);
+    ICAPModXact::AsyncStart(icapHeadSource.getRaw());
+    return true;
 }
 
 void
-ClientHttpRequest::takeAdaptedHeaders(HttpMsg *msg)
+ClientHttpRequest::noteIcapHeadersAdapted()
 {
-    debug(85,3)("ClientHttpRequest::takeAdaptedHeaders() called\n");
     assert(cbdataReferenceValid(this));                // indicates bug
 
+    HttpMsg *msg = icapHeadSource->adapted.header;
+    assert(msg);
+
     if (HttpRequest *new_req = dynamic_cast<HttpRequest*>(msg)) {
         /*
-         * Replace the old request with the new request.  First,
-         * Move the "body_connection" over, then unlink old and
-         * link new to the http state.
+         * Replace the old request with the new request.
          */
         HTTPMSGUNLOCK(request);
         request = HTTPMSGLOCK(new_req);
@@ -1235,6 +1117,12 @@ ClientHttpRequest::takeAdaptedHeaders(HttpMsg *msg)
     } else if (HttpReply *new_rep = dynamic_cast<HttpReply*>(msg)) {
         debugs(85,3,HERE << "REQMOD reply is HTTP reply");
 
+        // subscribe to receive reply body
+        if (new_rep->body_pipe != NULL) {
+            icapBodySource = new_rep->body_pipe;
+            assert(icapBodySource->setConsumerIfNotLate(this));
+        }
+
         clientStreamNode *node = (clientStreamNode *)client_stream.tail->prev->data;
         clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
         repContext->createStoreEntry(request->method, request->flags);
@@ -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<clientReplyContext *>(node->data.getRaw());
+void
+ClientHttpRequest::handleIcapFailure()
+{
+    debugs(85,3, HERE << "handleIcapFailure");
 
-        assert (repContext);
+    const bool usedStore = storeEntry() && !storeEntry()->isEmpty();
+    const bool usedPipe = request->body_pipe != NULL &&
+        request->body_pipe->consumedSize() > 0;
 
-        // Note if this code is ever used, clientBuildError() should be modified to
-        // accept an errno arg
-        repContext->setReplyToError(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR,
-                                    request->method, NULL,
-                                    getConn().getRaw() != NULL ? &getConn()->peer.sin_addr : &no_addr, request,
-                                    NULL, getConn().getRaw() != NULL
-                                    && getConn()->auth_user_request ? getConn()->
-                                    auth_user_request : request->auth_user_request, errno);
+    // XXX: we must not try to recover if the ICAP service is not bypassable!
 
-        node = (clientStreamNode *)client_stream.tail->data;
+    if (!usedStore && !usedPipe) {
+        debug(85,2)("WARNING: ICAP REQMOD callout failed, proceeding with original request\n");
+        if (calloutContext)
+            doCallouts();
+        return;
+    }
 
-        clientStreamRead(node, this, node->readBuffer);
+    debugs(85,3, HERE << "ICAP REQMOD callout failed, responding with error");
 
-#endif
+    clientStreamNode *node = (clientStreamNode *)client_stream.tail->prev->data;
+    clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
+    assert(repContext);
 
-        return;
-    }
+    // The original author of the code also wanted to pass an errno to 
+    // setReplyToError, but it seems unlikely that the errno reflects the
+    // true cause of the error at this point, so I did not pass it.
+    ConnStateData::Pointer c = getConn();
+    repContext->setReplyToError(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR,
+        request->method, NULL,
+        (c != NULL ? &c->peer.sin_addr : &no_addr), request, NULL,
+        (c != NULL && c->auth_user_request ?
+            c->auth_user_request : request->auth_user_request));
 
-    debug(0,0)("write me at %s:%d\n", __FILE__,__LINE__);
+    node = (clientStreamNode *)client_stream.tail->data;
+    clientStreamRead(node, this, node->readBuffer);
 }
 
 #endif
index 535f634147f8327f4f3c066c7a5ae335f87e0548..671329e0abdc9c0fb8f0a85687652f3a83b9e141 100644 (file)
@@ -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
index 5ead65777622d394f2cf1ab86dc10dbf091bc1ad..7d114230e7b4e1cca28c4f5a9871e8b3faf26af8 100644 (file)
@@ -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 */
index cd70c4d49481248dc41b6c48482f55a7fc4d2735..b18f8a0cd3dd822c952a637ab41a0ab548c313bf 100644 (file)
@@ -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
index 14d9459b53f25283a31eca21db0c8eadda6df9d1..6c7dd5073aa2faf283e5642e8661e68e4949dc18 100644 (file)
@@ -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<HttpStateData *>(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<HttpStateData *>(data);
-    httpState->requestBodyHandler(mb);
-}
-
-void
-HttpStateData::requestBodyHandler(MemBuf &mb)
+HttpStateData::handleMoreRequestBodyAvailable()
 {
     if (eof || fd < 0) {
+        // XXX: we should check this condition in other callbacks then!
+        // TODO: Check whether this can actually happen: We should unsubscribe
+        // as a body consumer when the above condition(s) are detected.
         debugs(11, 1, HERE << "Transaction aborted while reading HTTP body");
         return;
     }
 
-    if (mb.contentSize() > 0) {
+    assert(requestBodySource != NULL);
+    if (requestBodySource->buf().hasContent()) {
+        // XXX: why does not this trigger a debug message on every request?
         if (flags.headers_parsed && !flags.abuse_detected) {
             flags.abuse_detected = 1;
-            debug(11, 1) ("httpSendRequestEntryDone: Likely proxy abuse detected '%s' -> '%s'\n",
+            debug(11, 1) ("http handleMoreRequestBodyAvailable: Likely proxy abuse detected '%s' -> '%s'\n",
                           inet_ntoa(orig_request->client_addr),
                           storeUrl(entry));
 
@@ -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<HttpStateData *>(data);
-    httpState->sendRequestEntity(fd, size, errflag);
+    ServerStateData::handleRequestBodyProducerAborted();
+    // XXX: SendComplete(COMM_ERR_CLOSING) does little. Is it enough?
+    SendComplete(fd, NULL, 0, COMM_ERR_CLOSING, 0, this);
 }
 
+// called when we wrote request headers(!) or a part of the body
 void
-HttpStateData::sendRequestEntity(int fd, size_t size, comm_err_t errflag)
+HttpStateData::sentRequestBody(int fd, size_t size, comm_err_t errflag)
 {
-    debug(11, 5) ("httpSendRequestEntity: FD %d: size %d: errflag %d.\n",
-                  fd, (int) size, errflag);
-    debugs(32,3,HERE << "httpSendRequestEntity called");
-
-    /*
-     * This used to be an assertion for body_reader != NULL.
-     * Currently there are cases where body_reader may become NULL
-     * before reaching this point in the code.  This can happen
-     * because body_reader is attached to HttpRequest and other
-     * modules (client_side, ICAP) have access to HttpRequest->body
-     * reader.  An aborted transaction may cause body_reader to
-     * become NULL between the time sendRequestEntity was registered
-     * and actually called.  For now we'll abort the whole transaction,
-     * but this should be fixed so that the client/icap/server sides
-     * are cleaned up independently.
-     */
-
-    if (orig_request->body_reader == NULL) {
-        debugs(32,1,HERE << "sendRequestEntity body_reader became NULL, aborting transaction");
-        comm_close(fd);
-        return;
-    }
-
-    if (size > 0) {
-        fd_bytes(fd, size, FD_WRITE);
-        kb_incr(&statCounter.server.all.kbytes_out, size);
+    if (size > 0)
         kb_incr(&statCounter.server.http.kbytes_out, size);
+    ServerStateData::sentRequestBody(fd, size, errflag);
+}
 
-        if (flags.consume_body_data) {
-            orig_request->body_reader->consume(size);
-            orig_request->body_reader->bytes_read += size;
-            debugs(32,3," HTTP server body bytes_read=" << orig_request->body_reader->bytes_read);
-        }
-    }
-
-    if (errflag == COMM_ERR_CLOSING)
-        return;
-
-    if (errflag) {
-        ErrorState *err;
-        err = errorCon(ERR_WRITE_ERROR, HTTP_BAD_GATEWAY, fwd->request);
-        err->xerrno = errno;
-        fwd->fail(err);
-        comm_close(fd);
-        return;
-    }
-
-    if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+// Quickly abort the transaction
+// TODO: destruction should be sufficient as the destructor should cleanup,
+// including canceling close handlers
+void
+HttpStateData::abortTransaction(const char *reason)
+{
+    debugs(11,5, HERE << "aborting transaction for " << reason <<
+           "; FD " << fd << ", this " << this);
+    if (fd >= 0)
         comm_close(fd);
-        return;
-    }
-
-    size_t r = orig_request->body_reader->remaining();
-    debugs(32,3,HERE << "body remaining = " << r);
-
-    if (r) {
-        debugs(32,3,HERE << "reading more body data");
-        orig_request->body_reader->read(RequestBodyHandlerWrapper, this);
-    } else {
-        debugs(32,3,HERE << "done reading body data");
-        sendRequestEntityDone();
-    }
+    else
+        delete this;
 }
 
 void
@@ -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
index fcd3c09dc7d521cca1cadcee6d7878e628dd837c..8b7bf97b5efdfff3b2c68d7851cc227fcfb302ef 100644 (file)
@@ -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/
 #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:
index 07bd1f3e0d71b6a90d47d237ba86a932468ce913..16e579b902a5a7ad9c497cdf77dc28289cc6ce69 100644 (file)
@@ -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: