]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ICAP/ICAPServiceRep.cc
Bootstrapped
[thirdparty/squid.git] / src / ICAP / ICAPServiceRep.cc
CommitLineData
774c051c 1/*
507d0a78 2 * DEBUG: section 93 ICAP (RFC 3507) Client
774c051c 3 */
4
5#include "squid.h"
6#include "TextException.h"
c824c43b 7#include "HttpReply.h"
774c051c 8#include "ICAPServiceRep.h"
9#include "ICAPOptions.h"
10#include "ICAPOptXact.h"
11#include "ConfigParser.h"
5f8252d2 12#include "ICAPConfig.h"
985c86bc 13#include "SquidTime.h"
774c051c 14
15CBDATA_CLASS_INIT(ICAPServiceRep);
16
17ICAPServiceRep::ICAPServiceRep(): method(ICAP::methodNone),
c99de607 18 point(ICAP::pointNone), port(-1), bypass(false),
19 theOptions(NULL), theLastUpdate(0),
20 theSessionFailures(0), isSuspended(0),
21 waiting(false), notifying(false),
22 updateScheduled(false), self(NULL),
23 wasAnnouncedUp(true) // do not announce an "up" service at startup
774c051c 24{}
25
26ICAPServiceRep::~ICAPServiceRep()
27{
c99de607 28 Must(!waiting);
774c051c 29 changeOptions(0);
30}
31
32const char *
33ICAPServiceRep::methodStr() const
34{
35 return ICAP::methodStr(method);
36}
37
38ICAP::Method
39ICAPServiceRep::parseMethod(const char *str) const
40{
41 if (!strncasecmp(str, "REQMOD", 6))
42 return ICAP::methodReqmod;
43
44 if (!strncasecmp(str, "RESPMOD", 7))
45 return ICAP::methodRespmod;
46
47 return ICAP::methodNone;
48}
49
50
51const char *
52ICAPServiceRep::vectPointStr() const
53{
54 return ICAP::vectPointStr(point);
55}
56
57ICAP::VectPoint
58ICAPServiceRep::parseVectPoint(const char *service) const
59{
60 const char *t = service;
61 const char *q = strchr(t, '_');
62
63 if (q)
64 t = q + 1;
65
66 if (!strcasecmp(t, "precache"))
67 return ICAP::pointPreCache;
68
69 if (!strcasecmp(t, "postcache"))
70 return ICAP::pointPostCache;
71
72 return ICAP::pointNone;
73}
74
75bool
76ICAPServiceRep::configure(Pointer &aSelf)
77{
78 assert(!self && aSelf != NULL);
79 self = aSelf;
80
81 char *service_type = NULL;
82
30abd221 83 ConfigParser::ParseString(&key);
774c051c 84 ConfigParser::ParseString(&service_type);
85 ConfigParser::ParseBool(&bypass);
30abd221 86 ConfigParser::ParseString(&uri);
774c051c 87
30abd221 88 debugs(3, 5, "ICAPService::parseConfigLine (line " << config_lineno << "): " << key.buf() << " " << service_type << " " << bypass);
774c051c 89
90 method = parseMethod(service_type);
91 point = parseVectPoint(service_type);
92
bf8fe701 93 debugs(3, 5, "ICAPService::parseConfigLine (line " << config_lineno << "): service is " << methodStr() << "_" << vectPointStr());
774c051c 94
30abd221 95 if (uri.cmp("icap://", 7) != 0) {
96 debugs(3, 0, "ICAPService::parseConfigLine (line " << config_lineno << "): wrong uri: " << uri.buf());
774c051c 97 return false;
98 }
99
30abd221 100 const char *s = uri.buf() + 7;
774c051c 101
102 const char *e;
103
104 bool have_port = false;
105
106 if ((e = strchr(s, ':')) != NULL) {
107 have_port = true;
108 } else if ((e = strchr(s, '/')) != NULL) {
109 have_port = false;
110 } else {
111 return false;
112 }
113
114 int len = e - s;
115 host.limitInit(s, len);
116 s = e;
117
118 if (have_port) {
119 s++;
120
121 if ((e = strchr(s, '/')) != NULL) {
122 char *t;
123 port = strtoul(s, &t, 0) % 65536;
124
125 if (t != e) {
126 return false;
127 }
128
129 s = e;
130
131 if (s[0] != '/') {
132 return false;
133 }
134 }
135 } else {
136
137 struct servent *serv = getservbyname("icap", "tcp");
138
139 if (serv) {
140 port = htons(serv->s_port);
141 } else {
142 port = 1344;
143 }
144 }
145
146 s++;
147 e = strchr(s, '\0');
148 len = e - s;
149
150 if (len > 1024) {
bf8fe701 151 debugs(3, 0, "icap_service_process (line " << config_lineno << "): long resource name (>1024), probably wrong");
774c051c 152 }
153
154 resource.limitInit(s, len + 1);
155
156 if ((bypass != 0) && (bypass != 1)) {
157 return false;
158 }
159
160 return true;
161
162};
163
164void ICAPServiceRep::invalidate()
165{
166 assert(self != NULL);
c99de607 167 Pointer savedSelf = self; // to prevent destruction when we nullify self
168 self = NULL;
169
170 announceStatusChange("invalidated by reconfigure", false);
171
172 savedSelf = NULL; // may destroy us and, hence, invalidate cbdata(this)
774c051c 173 // TODO: it would be nice to invalidate cbdata(this) when not destroyed
174}
175
c99de607 176void ICAPServiceRep::noteFailure() {
177 ++theSessionFailures;
178 debugs(93,4, "ICAPService failure " << theSessionFailures <<
5f8252d2 179 ", out of " << TheICAPConfig.service_failure_limit << " allowed");
c99de607 180
5f8252d2 181 if (TheICAPConfig.service_failure_limit >= 0 &&
182 theSessionFailures > TheICAPConfig.service_failure_limit)
c99de607 183 suspend("too many failures");
184
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.
189}
190
191void ICAPServiceRep::suspend(const char *reason) {
192 if (isSuspended) {
193 debugs(93,4, "keeping ICAPService suspended, also for " << reason);
194 } else {
195 isSuspended = reason;
196 debugs(93,1, "suspending ICAPService for " << reason);
197 announceStatusChange("suspended", true);
198 }
199}
200
201bool ICAPServiceRep::probed() const
202{
203 return theLastUpdate != 0;
204}
205
206bool ICAPServiceRep::hasOptions() const {
207 return theOptions && theOptions->valid() && theOptions->fresh();
208}
209
774c051c 210bool ICAPServiceRep::up() const
211{
c99de607 212 return self != NULL && !isSuspended && hasOptions();
213}
214
215bool ICAPServiceRep::broken() const
216{
217 return probed() && !up();
218}
219
30abd221 220bool ICAPServiceRep::wantsUrl(const String &urlPath) const
c99de607 221{
222 Must(hasOptions());
223 return theOptions->transferKind(urlPath) != ICAPOptions::xferIgnore;
774c051c 224}
225
30abd221 226bool ICAPServiceRep::wantsPreview(const String &urlPath, size_t &wantedSize) const
774c051c 227{
c99de607 228 Must(hasOptions());
774c051c 229
230 if (theOptions->preview < 0)
231 return false;
232
c99de607 233 if (theOptions->transferKind(urlPath) != ICAPOptions::xferPreview)
234 return false;
235
774c051c 236 wantedSize = theOptions->preview;
237
238 return true;
239}
240
241bool ICAPServiceRep::allows204() const
242{
c99de607 243 Must(hasOptions());
774c051c 244 return true; // in the future, we may have ACLs to prevent 204s
245}
246
247
248static
249void ICAPServiceRep_noteTimeToUpdate(void *data)
250{
251 ICAPServiceRep *service = static_cast<ICAPServiceRep*>(data);
252 Must(service);
253 service->noteTimeToUpdate();
254}
255
256void ICAPServiceRep::noteTimeToUpdate()
257{
c99de607 258 if (self != NULL)
259 updateScheduled = false;
260
261 if (!self || waiting) {
774c051c 262 debugs(93,5, "ICAPService ignores options update " << status());
263 return;
264 }
265
266 debugs(93,5, "ICAPService performs a regular options update " << status());
267 startGettingOptions();
268}
269
270static
271void ICAPServiceRep_noteTimeToNotify(void *data)
272{
273 ICAPServiceRep *service = static_cast<ICAPServiceRep*>(data);
274 Must(service);
275 service->noteTimeToNotify();
276}
277
278void ICAPServiceRep::noteTimeToNotify()
279{
280 Must(!notifying);
281 notifying = true;
282 debugs(93,7, "ICAPService notifies " << theClients.size() << " clients " <<
283 status());
284
285 // note: we must notify even if we are invalidated
286
287 Pointer us = NULL;
288
289 while (!theClients.empty()) {
290 Client i = theClients.pop_back();
291 us = i.service; // prevent callbacks from destroying us while we loop
292
293 if (cbdataReferenceValid(i.data))
294 (*i.callback)(i.data, us);
295
296 cbdataReferenceDone(i.data);
297 }
298
299 notifying = false;
300}
301
302void ICAPServiceRep::callWhenReady(Callback *cb, void *data)
303{
5f8252d2 304 debugs(93,5, HERE << "ICAPService is asked to call " << data <<
305 " when ready " << status());
306
774c051c 307 Must(cb);
308 Must(self != NULL);
c99de607 309 Must(!broken()); // we do not wait for a broken service
774c051c 310
311 Client i;
312 i.service = self;
313 i.callback = cb;
314 i.data = cbdataReference(data);
315 theClients.push_back(i);
316
c99de607 317 if (waiting || notifying)
774c051c 318 return; // do nothing, we will be picked up in noteTimeToNotify()
319
320 if (needNewOptions())
321 startGettingOptions();
322 else
323 scheduleNotification();
324}
325
326void ICAPServiceRep::scheduleNotification()
327{
328 debugs(93,7, "ICAPService will notify " << theClients.size() << " clients");
329 eventAdd("ICAPServiceRep::noteTimeToNotify", &ICAPServiceRep_noteTimeToNotify, this, 0, 0, true);
330}
331
774c051c 332bool ICAPServiceRep::needNewOptions() const
333{
c99de607 334 return self != NULL && !up();
774c051c 335}
336
337void ICAPServiceRep::changeOptions(ICAPOptions *newOptions)
338{
5f8252d2 339 debugs(93,8, "ICAPService changes options from " << theOptions << " to " <<
340 newOptions << ' ' << status());
c99de607 341
774c051c 342 delete theOptions;
343 theOptions = newOptions;
c99de607 344 theSessionFailures = 0;
345 isSuspended = 0;
346 theLastUpdate = squid_curtime;
347
348 checkOptions();
349 announceStatusChange("down after an options fetch failure", true);
350}
ffddf96c 351
c99de607 352void ICAPServiceRep::checkOptions()
353{
9d0cdbb9 354 if (theOptions == NULL)
355 return;
356
ffddf96c 357 /*
eadded2e 358 * Issue a warning if the ICAP server returned methods in the
359 * options response that don't match the method from squid.conf.
ffddf96c 360 */
361
eadded2e 362 if (!theOptions->methods.empty()) {
363 bool method_found = false;
30abd221 364 String method_list;
eadded2e 365 Vector <ICAP::Method>::iterator iter = theOptions->methods.begin();
eadded2e 366
367 while (iter != theOptions->methods.end()) {
eadded2e 368
369 if (*iter == method) {
370 method_found = true;
371 break;
372 }
373
374 method_list.append(ICAP::methodStr(*iter));
375 method_list.append(" ", 1);
376 iter++;
eadded2e 377 }
378
eadded2e 379 if (!method_found) {
380 debugs(93,1, "WARNING: Squid is configured to use ICAP method " <<
381 ICAP::methodStr(method) <<
30abd221 382 " for service " << uri.buf() <<
383 " but OPTIONS response declares the methods are " << method_list.buf());
eadded2e 384 }
385 }
8eeb99bf 386
387
388 /*
389 * Check the ICAP server's date header for clock skew
390 */
5f8252d2 391 const int skew = (int)(theOptions->timestamp() - squid_curtime);
392 if (abs(skew) > theOptions->ttl()) {
393 // TODO: If skew is negative, the option will be considered down
394 // because of stale options. We should probably change this.
395 debugs(93, 1, "ICAP service's clock is skewed by " << skew <<
30abd221 396 " seconds: " << uri.buf());
5f8252d2 397 }
c99de607 398}
8eeb99bf 399
c99de607 400void ICAPServiceRep::announceStatusChange(const char *downPhrase, bool important) const
401{
402 if (wasAnnouncedUp == up()) // no significant changes to announce
403 return;
9d0cdbb9 404
c99de607 405 const char *what = bypass ? "optional" : "essential";
406 const char *state = wasAnnouncedUp ? downPhrase : "up";
407 const int level = important ? 1 : 2;
5f8252d2 408 debugs(93,level, what << " ICAP service is " << state << ": " << uri <<
409 ' ' << status());
9d0cdbb9 410
c99de607 411 wasAnnouncedUp = !wasAnnouncedUp;
774c051c 412}
413
c824c43b 414// we are receiving ICAP OPTIONS response headers here or NULL on failures
415void ICAPServiceRep::noteIcapAnswer(HttpMsg *msg)
774c051c 416{
c824c43b 417 Must(waiting);
418 waiting = false;
774c051c 419
c824c43b 420 Must(msg);
421
422 debugs(93,5, "ICAPService is interpreting new options " << status());
5f8252d2 423
c824c43b 424 ICAPOptions *newOptions = NULL;
425 if (HttpReply *r = dynamic_cast<HttpReply*>(msg)) {
426 newOptions = new ICAPOptions;
427 newOptions->configure(r);
428 } else {
429 debugs(93,1, "ICAPService got wrong options message " << status());
430 }
431
432 handleNewOptions(newOptions);
433}
434
435void ICAPServiceRep::noteIcapQueryAbort(bool) {
c99de607 436 Must(waiting);
437 waiting = false;
774c051c 438
c824c43b 439 debugs(93,3, "ICAPService failed to fetch options " << status());
440 handleNewOptions(0);
441}
442
443void ICAPServiceRep::handleNewOptions(ICAPOptions *newOptions)
444{
445 // new options may be NULL
5f8252d2 446 changeOptions(newOptions);
774c051c 447
c99de607 448 debugs(93,3, "ICAPService got new options and is now " << status());
774c051c 449
450 scheduleUpdate();
774c051c 451 scheduleNotification();
452}
453
454void ICAPServiceRep::startGettingOptions()
455{
c99de607 456 Must(!waiting);
774c051c 457 debugs(93,6, "ICAPService will get new options " << status());
c99de607 458 waiting = true;
774c051c 459
c824c43b 460 initiateIcap(new ICAPOptXactLauncher(this, self));
c99de607 461 // TODO: timeout in case ICAPOptXact never calls us back?
c824c43b 462 // Such a timeout should probably be a generic AsyncStart feature.
774c051c 463}
464
465void ICAPServiceRep::scheduleUpdate()
466{
c99de607 467 if (updateScheduled)
468 return; // already scheduled
469
470 // XXX: move hard-coded constants from here to TheICAPConfig
471
472 // conservative estimate of how long the OPTIONS transaction will take
473 const int expectedWait = 20; // seconds
474
475 time_t when = 0;
774c051c 476
477 if (theOptions && theOptions->valid()) {
478 const time_t expire = theOptions->expire();
c99de607 479 debugs(93,7, "ICAPService options expire on " << expire << " >= " << squid_curtime);
774c051c 480
5f8252d2 481 // Unknown or invalid (too small) expiration times should not happen.
482 // ICAPOptions should use the default TTL, and ICAP servers should not
483 // send invalid TTLs, but bugs and attacks happen.
484 if (expire < expectedWait)
c99de607 485 when = squid_curtime + 60*60;
486 else
487 when = expire - expectedWait; // before the current options expire
774c051c 488 } else {
5f8252d2 489 // delay for a down service
490 when = squid_curtime + TheICAPConfig.service_revival_delay;
774c051c 491 }
492
5f8252d2 493 debugs(93,7, "ICAPService options raw update at " << when << " or in " <<
494 (when - squid_curtime) << " sec");
495
496 /* adjust update time to prevent too-frequent updates */
497
c99de607 498 if (when < squid_curtime)
499 when = squid_curtime;
500
5f8252d2 501 const int minUpdateGap = expectedWait + 10; // seconds
c99de607 502 if (when < theLastUpdate + minUpdateGap)
503 when = theLastUpdate + minUpdateGap;
774c051c 504
c99de607 505 const int delay = when - squid_curtime;
c99de607 506 debugs(93,5, "ICAPService will update options in " << delay << " sec");
774c051c 507 eventAdd("ICAPServiceRep::noteTimeToUpdate",
508 &ICAPServiceRep_noteTimeToUpdate, this, delay, 0, true);
c99de607 509 updateScheduled = true;
774c051c 510}
511
c99de607 512// returns a temporary string depicting service status, for debugging
774c051c 513const char *ICAPServiceRep::status() const
514{
c99de607 515 static MemBuf buf;
516
517 buf.reset();
518 buf.append("[", 1);
519
520 if (up())
521 buf.append("up", 2);
5f8252d2 522 else {
c99de607 523 buf.append("down", 4);
5f8252d2 524 if (!self)
525 buf.append(",gone", 5);
526 if (isSuspended)
527 buf.append(",susp", 5);
c99de607 528
5f8252d2 529 if (!theOptions)
530 buf.append(",!opt", 5);
531 else
532 if (!theOptions->valid())
533 buf.append(",!valid", 7);
534 else
535 if (!theOptions->fresh())
536 buf.append(",stale", 6);
537 }
774c051c 538
c99de607 539 if (waiting)
540 buf.append(",wait", 5);
774c051c 541
c99de607 542 if (notifying)
543 buf.append(",notif", 6);
774c051c 544
c99de607 545 if (theSessionFailures > 0)
5f8252d2 546 buf.Printf(",fail%d", theSessionFailures);
774c051c 547
c99de607 548 buf.append("]", 1);
549 buf.terminate();
774c051c 550
c99de607 551 return buf.content();
774c051c 552}