]> git.ipfire.org Git - thirdparty/squid.git/blame - src/adaptation/icap/Xaction.cc
Compatibility fixes for Solaris/gcc
[thirdparty/squid.git] / src / adaptation / icap / Xaction.cc
CommitLineData
774c051c 1/*
507d0a78 2 * DEBUG: section 93 ICAP (RFC 3507) Client
774c051c 3 */
4
5#include "squid.h"
6#include "comm.h"
bd7f2ede 7#include "CommCalls.h"
5f8252d2 8#include "HttpMsg.h"
26cc52cb 9#include "adaptation/icap/Xaction.h"
3ff65596 10#include "adaptation/icap/Launcher.h"
26cc52cb 11#include "adaptation/icap/Config.h"
3d93a84d 12#include "base/TextException.h"
781ce8ff 13#include "pconn.h"
3ff65596
AR
14#include "HttpRequest.h"
15#include "HttpReply.h"
e6713f4e 16#include "ip/tools.h"
3ff65596
AR
17#include "acl/FilledChecklist.h"
18#include "icap_log.h"
781ce8ff 19#include "fde.h"
3ff65596 20#include "SquidTime.h"
781ce8ff 21
22static PconnPool *icapPconnPool = new PconnPool("ICAP Servers");
774c051c 23
5f8252d2 24
26cc52cb 25//CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap, Xaction);
5f8252d2 26
26cc52cb 27Adaptation::Icap::Xaction::Xaction(const char *aTypeName, Adaptation::Initiator *anInitiator, Adaptation::Icap::ServiceRep::Pointer &aService):
bd7f2ede 28 AsyncJob(aTypeName),
a22e6cd3 29 Adaptation::Initiate(aTypeName, anInitiator),
3ff65596
AR
30 icapRequest(NULL),
31 icapReply(NULL),
32 attempts(0),
774c051c 33 connection(-1),
a22e6cd3 34 theService(aService),
774c051c 35 commBuf(NULL), commBufSize(0),
36 commEof(false),
2dfede9e 37 reuseConnection(true),
c824c43b 38 isRetriable(true),
3ff65596 39 isRepeatable(true),
cfc68405 40 ignoreLastWrite(false),
c824c43b 41 connector(NULL), reader(NULL), writer(NULL), closer(NULL)
774c051c 42{
5f8252d2 43 debugs(93,3, typeName << " constructed, this=" << this <<
9e008dda 44 " [icapx" << id << ']'); // we should not call virtual status() here
3ff65596
AR
45 icapRequest = HTTPMSGLOCK(new HttpRequest);
46 icap_tr_start = current_time;
774c051c 47}
48
26cc52cb 49Adaptation::Icap::Xaction::~Xaction()
774c051c 50{
5f8252d2 51 debugs(93,3, typeName << " destructed, this=" << this <<
9e008dda 52 " [icapx" << id << ']'); // we should not call virtual status() here
3ff65596
AR
53 HTTPMSGUNLOCK(icapRequest);
54 HTTPMSGUNLOCK(icapReply);
5f8252d2 55}
56
26cc52cb
AR
57Adaptation::Icap::ServiceRep &
58Adaptation::Icap::Xaction::service()
0bef8dd7 59{
a22e6cd3
AR
60 Must(theService != NULL);
61 return *theService;
0bef8dd7
AR
62}
63
26cc52cb 64void Adaptation::Icap::Xaction::disableRetries()
9e008dda 65{
3ff65596
AR
66 debugs(93,5, typeName << (isRetriable ? " from now on" : " still") <<
67 " cannot be retried " << status());
c824c43b 68 isRetriable = false;
69}
70
3ff65596
AR
71void Adaptation::Icap::Xaction::disableRepeats(const char *reason)
72{
73 debugs(93,5, typeName << (isRepeatable ? " from now on" : " still") <<
74 " cannot be repeated because " << reason << status());
75 isRepeatable = false;
76}
77
26cc52cb 78void Adaptation::Icap::Xaction::start()
5f8252d2 79{
0bef8dd7 80 Adaptation::Initiate::start();
5f8252d2 81
82 readBuf.init(SQUID_TCP_SO_RCVBUF, SQUID_TCP_SO_RCVBUF);
83 commBuf = (char*)memAllocBuf(SQUID_TCP_SO_RCVBUF, &commBufSize);
84 // make sure maximum readBuf space does not exceed commBuf size
85 Must(static_cast<size_t>(readBuf.potentialSpaceSize()) <= commBufSize);
774c051c 86}
87
88// TODO: obey service-specific, OPTIONS-reported connection limit
26cc52cb 89void Adaptation::Icap::Xaction::openConnection()
774c051c 90{
b7ac5457 91 Ip::Address client_addr;
7aa6b7cb 92
c824c43b 93 Must(connection < 0);
94
0bef8dd7 95 const Adaptation::Service &s = service();
774c051c 96
26cc52cb 97 if (!TheConfig.reuse_connections)
560d7d2d 98 disableRetries(); // this will also safely drain pconn pool
99
c8ceec27 100 // TODO: check whether NULL domain is appropriate here
a7a42b14 101 connection = icapPconnPool->pop(s.cfg().host.termedBuf(), s.cfg().port, NULL, client_addr, isRetriable);
c8ceec27 102 if (connection >= 0) {
103 debugs(93,3, HERE << "reused pconn FD " << connection);
bd7f2ede 104
105 // fake the connect callback
26cc52cb
AR
106 // TODO: can we sync call Adaptation::Icap::Xaction::noteCommConnected here instead?
107 typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommConnectCbParams> Dialer;
108 Dialer dialer(this, &Adaptation::Icap::Xaction::noteCommConnected);
af6a12ee 109 dialer.params.fd = connection;
bd7f2ede 110 dialer.params.flag = COMM_OK;
111 // fake other parameters by copying from the existing connection
26cc52cb 112 connector = asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommConnected", dialer);
9e008dda 113 ScheduleCallHere(connector);
c8ceec27 114 return;
2dfede9e 115 }
116
c8ceec27 117 disableRetries(); // we only retry pconn failures
118
b7ac5457 119 Ip::Address outgoing;
e6713f4e
AJ
120 if (!Ip::EnableIpv6 && !outgoing.SetIPv4()) {
121 debugs(31, DBG_CRITICAL, "ERROR: IPv6 is disabled. " << outgoing << " is not an IPv4 address.");
122 dieOnConnectionFailure(); // throws
123 }
124 /* split-stack for now requires default IPv4-only socket */
125 if (Ip::EnableIpv6&IPV6_SPECIAL_SPLITSTACK && outgoing.IsAnyAddr() && !s.cfg().ipv6) {
126 outgoing.SetIPv4();
127 }
128
9e008dda 129 connection = comm_open(SOCK_STREAM, 0, outgoing,
a7a42b14 130 COMM_NONBLOCKING, s.cfg().uri.termedBuf());
774c051c 131
c824c43b 132 if (connection < 0)
133 dieOnConnectionFailure(); // throws
774c051c 134
a7a42b14 135 debugs(93,3, typeName << " opens connection to " << s.cfg().host << ":" << s.cfg().port);
774c051c 136
cfc68405 137 // TODO: service bypass status may differ from that of a transaction
26cc52cb
AR
138 typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommTimeoutCbParams> TimeoutDialer;
139 AsyncCall::Pointer timeoutCall = asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommTimedout",
140 TimeoutDialer(this,&Adaptation::Icap::Xaction::noteCommTimedout));
774c051c 141
26cc52cb 142 commSetTimeout(connection, TheConfig.connect_timeout(
9e008dda 143 service().cfg().bypass), timeoutCall);
774c051c 144
26cc52cb
AR
145 typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommCloseCbParams> CloseDialer;
146 closer = asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommClosed",
147 CloseDialer(this,&Adaptation::Icap::Xaction::noteCommClosed));
bd7f2ede 148 comm_add_close_handler(connection, closer);
149
26cc52cb
AR
150 typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommConnectCbParams> ConnectDialer;
151 connector = asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommConnected",
152 ConnectDialer(this, &Adaptation::Icap::Xaction::noteCommConnected));
a7a42b14 153 commConnectStart(connection, s.cfg().host.termedBuf(), s.cfg().port, connector);
774c051c 154}
155
2dfede9e 156/*
157 * This event handler is necessary to work around the no-rentry policy
26cc52cb 158 * of Adaptation::Icap::Xaction::callStart()
2dfede9e 159 */
bd7f2ede 160#if 0
2dfede9e 161void
26cc52cb 162Adaptation::Icap::Xaction::reusedConnection(void *data)
2dfede9e 163{
192378eb 164 debugs(93, 5, HERE << "reused connection");
26cc52cb 165 Adaptation::Icap::Xaction *x = (Adaptation::Icap::Xaction*)data;
2dfede9e 166 x->noteCommConnected(COMM_OK);
167}
bd7f2ede 168#endif
2dfede9e 169
26cc52cb 170void Adaptation::Icap::Xaction::closeConnection()
774c051c 171{
172 if (connection >= 0) {
774c051c 173
bd7f2ede 174 if (closer != NULL) {
175 comm_remove_close_handler(connection, closer);
774c051c 176 closer = NULL;
177 }
178
c99de607 179 cancelRead(); // may not work
180
5f8252d2 181 if (reuseConnection && !doneWithIo()) {
dd6e6148 182 //status() adds leading spaces.
5f8252d2 183 debugs(93,5, HERE << "not reusing pconn due to pending I/O" << status());
c99de607 184 reuseConnection = false;
185 }
774c051c 186
2dfede9e 187 if (reuseConnection) {
b7ac5457 188 Ip::Address client_addr;
dd6e6148 189 //status() adds leading spaces.
5f8252d2 190 debugs(93,3, HERE << "pushing pconn" << status());
9e008dda
AJ
191 AsyncCall::Pointer call = NULL;
192 commSetTimeout(connection, -1, call);
a7a42b14 193 icapPconnPool->push(connection, theService->cfg().host.termedBuf(),
9e008dda 194 theService->cfg().port, NULL, client_addr);
c824c43b 195 disableRetries();
2dfede9e 196 } else {
dd6e6148 197 //status() adds leading spaces.
5f8252d2 198 debugs(93,3, HERE << "closing pconn" << status());
c99de607 199 // comm_close will clear timeout
2dfede9e 200 comm_close(connection);
201 }
774c051c 202
c99de607 203 writer = NULL;
204 reader = NULL;
774c051c 205 connector = NULL;
206 connection = -1;
207 }
208}
209
210// connection with the ICAP service established
26cc52cb 211void Adaptation::Icap::Xaction::noteCommConnected(const CommConnectCbParams &io)
774c051c 212{
bd7f2ede 213 Must(connector != NULL);
774c051c 214 connector = NULL;
c99de607 215
bd7f2ede 216 if (io.flag != COMM_OK)
c99de607 217 dieOnConnectionFailure(); // throws
218
219 fd_table[connection].noteUse(icapPconnPool);
774c051c 220
221 handleCommConnected();
774c051c 222}
223
26cc52cb 224void Adaptation::Icap::Xaction::dieOnConnectionFailure()
9e008dda 225{
4932ad93 226 debugs(93, 2, HERE << typeName <<
9e008dda 227 " failed to connect to " << service().cfg().uri);
c99de607 228 throw TexcHere("cannot connect to the ICAP service");
229}
230
26cc52cb 231void Adaptation::Icap::Xaction::scheduleWrite(MemBuf &buf)
774c051c 232{
233 // comm module will free the buffer
26cc52cb
AR
234 typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommIoCbParams> Dialer;
235 writer = asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommWrote",
236 Dialer(this, &Adaptation::Icap::Xaction::noteCommWrote));
bd7f2ede 237
238 comm_write_mbuf(connection, &buf, writer);
c99de607 239 updateTimeout();
774c051c 240}
241
26cc52cb 242void Adaptation::Icap::Xaction::noteCommWrote(const CommIoCbParams &io)
774c051c 243{
bd7f2ede 244 Must(writer != NULL);
774c051c 245 writer = NULL;
9e008dda 246
cfc68405 247 if (ignoreLastWrite) {
248 // a hack due to comm inability to cancel a pending write
9e008dda 249 ignoreLastWrite = false;
bd7f2ede 250 debugs(93, 7, HERE << "ignoring last write; status: " << io.flag);
cfc68405 251 } else {
bd7f2ede 252 Must(io.flag == COMM_OK);
3ff65596 253 al.icap.bytesSent += io.size;
cfc68405 254 updateTimeout();
bd7f2ede 255 handleCommWrote(io.size);
cfc68405 256 }
774c051c 257}
258
259// communication timeout with the ICAP service
26cc52cb 260void Adaptation::Icap::Xaction::noteCommTimedout(const CommTimeoutCbParams &io)
774c051c 261{
774c051c 262 handleCommTimedout();
774c051c 263}
264
26cc52cb 265void Adaptation::Icap::Xaction::handleCommTimedout()
774c051c 266{
4932ad93 267 debugs(93, 2, HERE << typeName << " failed: timeout with " <<
9e008dda 268 theService->cfg().methodStr() << " " <<
a7a42b14 269 theService->cfg().uri << status());
fe3e2600 270 reuseConnection = false;
bd7f2ede 271 throw TexcHere(connector != NULL ?
9e008dda
AJ
272 "timed out while connecting to the ICAP service" :
273 "timed out while talking to the ICAP service");
774c051c 274}
275
276// unexpected connection close while talking to the ICAP service
26cc52cb 277void Adaptation::Icap::Xaction::noteCommClosed(const CommCloseCbParams &io)
774c051c 278{
279 closer = NULL;
774c051c 280 handleCommClosed();
774c051c 281}
282
26cc52cb 283void Adaptation::Icap::Xaction::handleCommClosed()
774c051c 284{
285 mustStop("ICAP service connection externally closed");
286}
287
3ff65596
AR
288void Adaptation::Icap::Xaction::callException(const std::exception &e)
289{
290 setOutcome(xoError);
8277060a 291 service().noteFailure();
3ff65596
AR
292 Adaptation::Initiate::callException(e);
293}
294
295
26cc52cb 296void Adaptation::Icap::Xaction::callEnd()
774c051c 297{
c824c43b 298 if (doneWithIo()) {
299 debugs(93, 5, HERE << typeName << " done with I/O" << status());
300 closeConnection();
301 }
0bef8dd7 302 Adaptation::Initiate::callEnd(); // may destroy us
774c051c 303}
304
26cc52cb 305bool Adaptation::Icap::Xaction::doneAll() const
774c051c 306{
0bef8dd7 307 return !connector && !reader && !writer && Adaptation::Initiate::doneAll();
774c051c 308}
309
26cc52cb 310void Adaptation::Icap::Xaction::updateTimeout()
9e008dda 311{
bd7f2ede 312 if (reader != NULL || writer != NULL) {
c99de607 313 // restart the timeout before each I/O
314 // XXX: why does Config.Timeout lacks a write timeout?
cfc68405 315 // TODO: service bypass status may differ from that of a transaction
26cc52cb
AR
316 typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommTimeoutCbParams> TimeoutDialer;
317 AsyncCall::Pointer call = asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommTimedout",
318 TimeoutDialer(this,&Adaptation::Icap::Xaction::noteCommTimedout));
bd7f2ede 319
9e008dda 320 commSetTimeout(connection,
26cc52cb 321 TheConfig.io_timeout(service().cfg().bypass), call);
c99de607 322 } else {
323 // clear timeout when there is no I/O
324 // Do we need a lifetime timeout?
9e008dda 325 AsyncCall::Pointer call = NULL;
bd7f2ede 326 commSetTimeout(connection, -1, call);
c99de607 327 }
328}
329
26cc52cb 330void Adaptation::Icap::Xaction::scheduleRead()
774c051c 331{
332 Must(connection >= 0);
333 Must(!reader);
334 Must(readBuf.hasSpace());
335
774c051c 336 /*
26cc52cb 337 * See comments in Adaptation::Icap::Xaction.h about why we use commBuf
bb790702 338 * here instead of reading directly into readBuf.buf.
774c051c 339 */
26cc52cb
AR
340 typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommIoCbParams> Dialer;
341 reader = asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommRead",
342 Dialer(this, &Adaptation::Icap::Xaction::noteCommRead));
774c051c 343
bd7f2ede 344 comm_read(connection, commBuf, readBuf.spaceSize(), reader);
c99de607 345 updateTimeout();
774c051c 346}
347
348// comm module read a portion of the ICAP response for us
26cc52cb 349void Adaptation::Icap::Xaction::noteCommRead(const CommIoCbParams &io)
774c051c 350{
bd7f2ede 351 Must(reader != NULL);
774c051c 352 reader = NULL;
353
bd7f2ede 354 Must(io.flag == COMM_OK);
355 Must(io.size >= 0);
774c051c 356
3ff65596
AR
357 al.icap.bytesRead+=io.size;
358
c99de607 359 updateTimeout();
360
bd7f2ede 361 debugs(93, 3, HERE << "read " << io.size << " bytes");
774c051c 362
363 /*
26cc52cb 364 * See comments in Adaptation::Icap::Xaction.h about why we use commBuf
bb790702 365 * here instead of reading directly into readBuf.buf.
774c051c 366 */
367
bd7f2ede 368 if (io.size > 0) {
369 readBuf.append(commBuf, io.size);
c824c43b 370 disableRetries(); // because pconn did not fail
371 } else {
372 reuseConnection = false;
774c051c 373 commEof = true;
c824c43b 374 }
774c051c 375
bd7f2ede 376 handleCommRead(io.size);
774c051c 377}
378
26cc52cb 379void Adaptation::Icap::Xaction::cancelRead()
774c051c 380{
bd7f2ede 381 if (reader != NULL) {
382 comm_read_cancel(connection, reader);
383 reader = NULL;
774c051c 384 }
385}
386
26cc52cb 387bool Adaptation::Icap::Xaction::parseHttpMsg(HttpMsg *msg)
774c051c 388{
def17b6a 389 debugs(93, 5, HERE << "have " << readBuf.contentSize() << " head bytes to parse");
774c051c 390
391 http_status error = HTTP_STATUS_NONE;
392 const bool parsed = msg->parse(&readBuf, commEof, &error);
393 Must(parsed || !error); // success or need more data
394
395 if (!parsed) { // need more data
396 Must(mayReadMore());
397 msg->reset();
398 return false;
399 }
400
401 readBuf.consume(msg->hdr_sz);
402 return true;
403}
404
26cc52cb 405bool Adaptation::Icap::Xaction::mayReadMore() const
774c051c 406{
407 return !doneReading() && // will read more data
408 readBuf.hasSpace(); // have space for more data
409}
410
26cc52cb 411bool Adaptation::Icap::Xaction::doneReading() const
774c051c 412{
413 return commEof;
414}
415
26cc52cb 416bool Adaptation::Icap::Xaction::doneWriting() const
c99de607 417{
418 return !writer;
419}
420
26cc52cb 421bool Adaptation::Icap::Xaction::doneWithIo() const
c99de607 422{
423 return connection >= 0 && // or we could still be waiting to open it
9e008dda
AJ
424 !connector && !reader && !writer && // fast checks, some redundant
425 doneReading() && doneWriting();
c99de607 426}
427
c824c43b 428// initiator aborted
26cc52cb 429void Adaptation::Icap::Xaction::noteInitiatorAborted()
774c051c 430{
c824c43b 431
432 if (theInitiator) {
433 clearInitiator();
434 mustStop("initiator aborted");
c99de607 435 }
c824c43b 436
774c051c 437}
438
3ff65596
AR
439void Adaptation::Icap::Xaction::setOutcome(const Adaptation::Icap::XactOutcome &xo)
440{
441 if (al.icap.outcome != xoUnknown) {
442 debugs(93, 3, HERE << "Warning: reseting outcome: from " <<
e1381638 443 al.icap.outcome << " to " << xo);
3ff65596
AR
444 } else {
445 debugs(93, 4, HERE << xo);
446 }
447 al.icap.outcome = xo;
448}
449
5f8252d2 450// This 'last chance' method is called before a 'done' transaction is deleted.
451// It is wrong to call virtual methods from a destructor. Besides, this call
452// indicates that the transaction will terminate as planned.
26cc52cb 453void Adaptation::Icap::Xaction::swanSong()
774c051c 454{
5f8252d2 455 // kids should sing first and then call the parent method.
456
457 closeConnection(); // TODO: rename because we do not always close
458
459 if (!readBuf.isNull())
460 readBuf.clean();
461
462 if (commBuf)
463 memFreeBuf(commBufSize, commBuf);
774c051c 464
c824c43b 465 if (theInitiator)
3ff65596
AR
466 tellQueryAborted();
467
468 maybeLog();
774c051c 469
0bef8dd7 470 Adaptation::Initiate::swanSong();
774c051c 471}
472
e1381638
AJ
473void Adaptation::Icap::Xaction::tellQueryAborted()
474{
3ff65596
AR
475 Adaptation::Icap::Launcher *l = dynamic_cast<Adaptation::Icap::Launcher*>(theInitiator.ptr());
476 Adaptation::Icap::XactAbortInfo abortInfo(icapRequest, icapReply, retriable(), repeatable());
e1381638 477 CallJob(91, 5, __FILE__, __LINE__,
3ff65596
AR
478 "Adaptation::Icap::Launcher::noteXactAbort",
479 XactAbortCall(l, &Adaptation::Icap::Launcher::noteXactAbort, abortInfo) );
480 clearInitiator();
481}
482
483
e1381638
AJ
484void Adaptation::Icap::Xaction::maybeLog()
485{
486 if (IcapLogfileStatus == LOG_ENABLE) {
3ff65596
AR
487 ACLChecklist *checklist = new ACLFilledChecklist(::Config.accessList.icap, al.request, dash_str);
488 if (!::Config.accessList.icap || checklist->fastCheck()) {
489 finalizeLogInfo();
490 icapLogLog(&al, checklist);
491 }
492 accessLogFreeMemory(&al);
493 delete checklist;
494 }
495}
496
497void Adaptation::Icap::Xaction::finalizeLogInfo()
498{
499 //prepare log data
500 al.icp.opcode = ICP_INVALID;
e1381638 501
3ff65596
AR
502 const Adaptation::Icap::ServiceRep &s = service();
503 al.icap.hostAddr = s.cfg().host.termedBuf();
504 al.icap.serviceName = s.cfg().key;
505 al.icap.reqUri = s.cfg().uri;
e1381638 506
3ff65596
AR
507 al.icap.ioTime = tvSubMsec(icap_tio_start, icap_tio_finish);
508 al.icap.trTime = tvSubMsec(icap_tr_start, current_time);
509
510 al.icap.request = HTTPMSGLOCK(icapRequest);
511 if (icapReply) {
512 al.icap.reply = HTTPMSGLOCK(icapReply);
513 al.icap.resStatus = icapReply->sline.status;
514 }
515}
516
774c051c 517// returns a temporary string depicting transaction status, for debugging
26cc52cb 518const char *Adaptation::Icap::Xaction::status() const
774c051c 519{
520 static MemBuf buf;
521 buf.reset();
522
5f8252d2 523 buf.append(" [", 2);
774c051c 524
525 fillPendingStatus(buf);
526 buf.append("/", 1);
527 fillDoneStatus(buf);
528
5f8252d2 529 buf.Printf(" icapx%d]", id);
774c051c 530
531 buf.terminate();
532
533 return buf.content();
534}
535
26cc52cb 536void Adaptation::Icap::Xaction::fillPendingStatus(MemBuf &buf) const
774c051c 537{
538 if (connection >= 0) {
5f8252d2 539 buf.Printf("FD %d", connection);
774c051c 540
bd7f2ede 541 if (writer != NULL)
774c051c 542 buf.append("w", 1);
543
bd7f2ede 544 if (reader != NULL)
774c051c 545 buf.append("r", 1);
546
5f8252d2 547 buf.append(";", 1);
774c051c 548 }
549}
550
26cc52cb 551void Adaptation::Icap::Xaction::fillDoneStatus(MemBuf &buf) const
774c051c 552{
553 if (connection >= 0 && commEof)
554 buf.Printf("Comm(%d)", connection);
555
556 if (stopReason != NULL)
557 buf.Printf("Stopped");
558}
3cfc19b3 559
26cc52cb 560bool Adaptation::Icap::Xaction::fillVirginHttpHeader(MemBuf &buf) const
3cfc19b3 561{
562 return false;
563}