2
* DEBUG: section 93 ICAP (RFC 3507) Client
6
#include "TextException.h"
7
#include "ICAPServiceRep.h"
8
#include "ICAPOptions.h"
9
#include "ICAPOptXact.h"
10
#include "ConfigParser.h"
11
#include "SquidTime.h"
13
CBDATA_CLASS_INIT(ICAPServiceRep);
15
// XXX: move to squid.conf
16
const int ICAPServiceRep::TheSessionFailureLimit = 10;
18
ICAPServiceRep::ICAPServiceRep(): method(ICAP::methodNone),
19
point(ICAP::pointNone), port(-1), bypass(false),
20
theOptions(NULL), theLastUpdate(0),
21
theSessionFailures(0), isSuspended(0),
22
waiting(false), notifying(false),
23
updateScheduled(false), self(NULL),
24
wasAnnouncedUp(true) // do not announce an "up" service at startup
27
ICAPServiceRep::~ICAPServiceRep()
34
ICAPServiceRep::methodStr() const
36
return ICAP::methodStr(method);
40
ICAPServiceRep::parseMethod(const char *str) const
42
if (!strncasecmp(str, "REQMOD", 6))
43
return ICAP::methodReqmod;
45
if (!strncasecmp(str, "RESPMOD", 7))
46
return ICAP::methodRespmod;
48
return ICAP::methodNone;
53
ICAPServiceRep::vectPointStr() const
55
return ICAP::vectPointStr(point);
59
ICAPServiceRep::parseVectPoint(const char *service) const
61
const char *t = service;
62
const char *q = strchr(t, '_');
67
if (!strcasecmp(t, "precache"))
68
return ICAP::pointPreCache;
70
if (!strcasecmp(t, "postcache"))
71
return ICAP::pointPostCache;
73
return ICAP::pointNone;
77
ICAPServiceRep::configure(Pointer &aSelf)
79
assert(!self && aSelf != NULL);
82
char *service_type = NULL;
84
ConfigParser::ParseString(&key);
85
ConfigParser::ParseString(&service_type);
86
ConfigParser::ParseBool(&bypass);
87
ConfigParser::ParseString(&uri);
89
debug(3, 5) ("ICAPService::parseConfigLine (line %d): %s %s %d\n", config_lineno, key.buf(), service_type, bypass);
91
method = parseMethod(service_type);
92
point = parseVectPoint(service_type);
94
debug(3, 5) ("ICAPService::parseConfigLine (line %d): service is %s_%s\n", config_lineno, methodStr(), vectPointStr());
96
if (uri.cmp("icap://", 7) != 0) {
97
debug(3, 0) ("ICAPService::parseConfigLine (line %d): wrong uri: %s\n", config_lineno, uri.buf());
101
const char *s = uri.buf() + 7;
105
bool have_port = false;
107
if ((e = strchr(s, ':')) != NULL) {
109
} else if ((e = strchr(s, '/')) != NULL) {
116
host.limitInit(s, len);
122
if ((e = strchr(s, '/')) != NULL) {
124
port = strtoul(s, &t, 0) % 65536;
138
struct servent *serv = getservbyname("icap", "tcp");
141
port = htons(serv->s_port);
152
debug(3, 0) ("icap_service_process (line %d): long resource name (>1024), probably wrong\n", config_lineno);
155
resource.limitInit(s, len + 1);
157
if ((bypass != 0) && (bypass != 1)) {
165
void ICAPServiceRep::invalidate()
167
assert(self != NULL);
168
Pointer savedSelf = self; // to prevent destruction when we nullify self
171
announceStatusChange("invalidated by reconfigure", false);
173
savedSelf = NULL; // may destroy us and, hence, invalidate cbdata(this)
174
// TODO: it would be nice to invalidate cbdata(this) when not destroyed
177
void ICAPServiceRep::noteFailure() {
178
++theSessionFailures;
179
debugs(93,4, "ICAPService failure " << theSessionFailures <<
180
", out of " << TheSessionFailureLimit << " allowed");
182
if (theSessionFailures > TheSessionFailureLimit)
183
suspend("too many failures");
185
// TODO: Should bypass setting affect how much Squid tries to talk to
186
// the ICAP service that is currently unusable and is likely to remain
187
// so for some time? The current code says "no". Perhaps the answer
188
// should be configurable.
191
void ICAPServiceRep::suspend(const char *reason) {
193
debugs(93,4, "keeping ICAPService suspended, also for " << reason);
195
isSuspended = reason;
196
debugs(93,1, "suspending ICAPService for " << reason);
197
announceStatusChange("suspended", true);
201
bool ICAPServiceRep::probed() const
203
return theLastUpdate != 0;
206
bool ICAPServiceRep::hasOptions() const {
207
return theOptions && theOptions->valid() && theOptions->fresh();
210
bool ICAPServiceRep::up() const
212
return self != NULL && !isSuspended && hasOptions();
215
bool ICAPServiceRep::broken() const
217
return probed() && !up();
220
bool ICAPServiceRep::wantsUrl(const String &urlPath) const
223
return theOptions->transferKind(urlPath) != ICAPOptions::xferIgnore;
226
bool ICAPServiceRep::wantsPreview(const String &urlPath, size_t &wantedSize) const
230
if (theOptions->preview < 0)
233
if (theOptions->transferKind(urlPath) != ICAPOptions::xferPreview)
236
wantedSize = theOptions->preview;
241
bool ICAPServiceRep::allows204() const
244
return true; // in the future, we may have ACLs to prevent 204s
249
void ICAPServiceRep_noteTimeToUpdate(void *data)
251
ICAPServiceRep *service = static_cast<ICAPServiceRep*>(data);
253
service->noteTimeToUpdate();
256
void ICAPServiceRep::noteTimeToUpdate()
259
updateScheduled = false;
261
if (!self || waiting) {
262
debugs(93,5, "ICAPService ignores options update " << status());
266
debugs(93,5, "ICAPService performs a regular options update " << status());
267
startGettingOptions();
271
void ICAPServiceRep_noteTimeToNotify(void *data)
273
ICAPServiceRep *service = static_cast<ICAPServiceRep*>(data);
275
service->noteTimeToNotify();
278
void ICAPServiceRep::noteTimeToNotify()
282
debugs(93,7, "ICAPService notifies " << theClients.size() << " clients " <<
285
// note: we must notify even if we are invalidated
289
while (!theClients.empty()) {
290
Client i = theClients.pop_back();
291
us = i.service; // prevent callbacks from destroying us while we loop
293
if (cbdataReferenceValid(i.data))
294
(*i.callback)(i.data, us);
296
cbdataReferenceDone(i.data);
302
void ICAPServiceRep::callWhenReady(Callback *cb, void *data)
306
Must(!broken()); // we do not wait for a broken service
311
i.data = cbdataReference(data);
312
theClients.push_back(i);
314
if (waiting || notifying)
315
return; // do nothing, we will be picked up in noteTimeToNotify()
317
if (needNewOptions())
318
startGettingOptions();
320
scheduleNotification();
323
void ICAPServiceRep::scheduleNotification()
325
debugs(93,7, "ICAPService will notify " << theClients.size() << " clients");
326
eventAdd("ICAPServiceRep::noteTimeToNotify", &ICAPServiceRep_noteTimeToNotify, this, 0, 0, true);
329
bool ICAPServiceRep::needNewOptions() const
331
return self != NULL && !up();
334
void ICAPServiceRep::changeOptions(ICAPOptions *newOptions)
336
debugs(93,9, "ICAPService changes options from " << theOptions << " to " <<
340
theOptions = newOptions;
341
theSessionFailures = 0;
343
theLastUpdate = squid_curtime;
346
announceStatusChange("down after an options fetch failure", true);
349
void ICAPServiceRep::checkOptions()
351
if (theOptions == NULL)
355
* Issue a warning if the ICAP server returned methods in the
356
* options response that don't match the method from squid.conf.
359
if (!theOptions->methods.empty()) {
360
bool method_found = false;
362
Vector <ICAP::Method>::iterator iter = theOptions->methods.begin();
364
while (iter != theOptions->methods.end()) {
366
if (*iter == method) {
371
method_list.append(ICAP::methodStr(*iter));
372
method_list.append(" ", 1);
377
debugs(93,1, "WARNING: Squid is configured to use ICAP method " <<
378
ICAP::methodStr(method) <<
379
" for service " << uri.buf() <<
380
" but OPTIONS response declares the methods are " << method_list.buf());
386
* Check the ICAP server's date header for clock skew
388
int skew = abs((int)(theOptions->timestamp() - squid_curtime));
389
if (skew > theOptions->ttl())
390
debugs(93, 1, host.buf() << "'s clock is skewed by " << skew << " seconds!");
393
void ICAPServiceRep::announceStatusChange(const char *downPhrase, bool important) const
395
if (wasAnnouncedUp == up()) // no significant changes to announce
398
const char *what = bypass ? "optional" : "essential";
399
const char *state = wasAnnouncedUp ? downPhrase : "up";
400
const int level = important ? 1 : 2;
401
debugs(93,level, what << " ICAP service is " << state << ": " << uri);
403
wasAnnouncedUp = !wasAnnouncedUp;
407
void ICAPServiceRep_noteNewOptions(ICAPOptXact *x, void *data)
409
ICAPServiceRep *service = static_cast<ICAPServiceRep*>(data);
411
service->noteNewOptions(x);
414
void ICAPServiceRep::noteNewOptions(ICAPOptXact *x)
420
changeOptions(x->options);
424
debugs(93,3, "ICAPService got new options and is now " << status());
427
scheduleNotification();
430
void ICAPServiceRep::startGettingOptions()
433
debugs(93,6, "ICAPService will get new options " << status());
436
ICAPOptXact *x = new ICAPOptXact;
437
x->start(self, &ICAPServiceRep_noteNewOptions, this);
438
// TODO: timeout in case ICAPOptXact never calls us back?
441
void ICAPServiceRep::scheduleUpdate()
444
return; // already scheduled
446
// XXX: move hard-coded constants from here to TheICAPConfig
448
// conservative estimate of how long the OPTIONS transaction will take
449
const int expectedWait = 20; // seconds
453
if (theOptions && theOptions->valid()) {
454
const time_t expire = theOptions->expire();
455
debugs(93,7, "ICAPService options expire on " << expire << " >= " << squid_curtime);
457
if (expire < 0) // unknown expiration time
458
when = squid_curtime + 60*60;
460
if (expire < expectedWait) // invalid expiration time
461
when = squid_curtime + 60*60;
463
when = expire - expectedWait; // before the current options expire
465
when = squid_curtime + 3*60; // delay for a down service
468
debugs(93,7, "ICAPService options raw update on " << when << " or " << (when - squid_curtime));
469
if (when < squid_curtime)
470
when = squid_curtime;
472
const int minUpdateGap = 1*60; // seconds
473
if (when < theLastUpdate + minUpdateGap)
474
when = theLastUpdate + minUpdateGap;
476
// TODO: keep the time of the last update to prevet too-frequent updates
478
const int delay = when - squid_curtime;
480
debugs(93,5, "ICAPService will update options in " << delay << " sec");
482
eventAdd("ICAPServiceRep::noteTimeToUpdate",
483
&ICAPServiceRep_noteTimeToUpdate, this, delay, 0, true);
484
updateScheduled = true;
487
// returns a temporary string depicting service status, for debugging
488
const char *ICAPServiceRep::status() const
498
buf.append("down", 4);
501
buf.append(",gone", 5);
504
buf.append(",wait", 5);
507
buf.append(",notif", 6);
509
if (theSessionFailures > 0)
510
buf.Printf(",F%d", theSessionFailures);
513
buf.append(",susp", 5);
518
return buf.content();