]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ICAP/ICAPServiceRep.cc
- Replaced BodyReader with BodyPipe. BodyReader was a
[thirdparty/squid.git] / src / ICAP / ICAPServiceRep.cc
CommitLineData
774c051c 1/*
2 * DEBUG: section 93 ICAP (RFC 3507) Client
3 */
4
5#include "squid.h"
6#include "TextException.h"
7#include "ICAPServiceRep.h"
8#include "ICAPOptions.h"
9#include "ICAPOptXact.h"
10#include "ConfigParser.h"
985c86bc 11#include "SquidTime.h"
774c051c 12
13CBDATA_CLASS_INIT(ICAPServiceRep);
14
c99de607 15// XXX: move to squid.conf
16const int ICAPServiceRep::TheSessionFailureLimit = 10;
17
774c051c 18ICAPServiceRep::ICAPServiceRep(): method(ICAP::methodNone),
c99de607 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
774c051c 25{}
26
27ICAPServiceRep::~ICAPServiceRep()
28{
c99de607 29 Must(!waiting);
774c051c 30 changeOptions(0);
31}
32
33const char *
34ICAPServiceRep::methodStr() const
35{
36 return ICAP::methodStr(method);
37}
38
39ICAP::Method
40ICAPServiceRep::parseMethod(const char *str) const
41{
42 if (!strncasecmp(str, "REQMOD", 6))
43 return ICAP::methodReqmod;
44
45 if (!strncasecmp(str, "RESPMOD", 7))
46 return ICAP::methodRespmod;
47
48 return ICAP::methodNone;
49}
50
51
52const char *
53ICAPServiceRep::vectPointStr() const
54{
55 return ICAP::vectPointStr(point);
56}
57
58ICAP::VectPoint
59ICAPServiceRep::parseVectPoint(const char *service) const
60{
61 const char *t = service;
62 const char *q = strchr(t, '_');
63
64 if (q)
65 t = q + 1;
66
67 if (!strcasecmp(t, "precache"))
68 return ICAP::pointPreCache;
69
70 if (!strcasecmp(t, "postcache"))
71 return ICAP::pointPostCache;
72
73 return ICAP::pointNone;
74}
75
76bool
77ICAPServiceRep::configure(Pointer &aSelf)
78{
79 assert(!self && aSelf != NULL);
80 self = aSelf;
81
82 char *service_type = NULL;
83
84 ConfigParser::ParseString(&key);
85 ConfigParser::ParseString(&service_type);
86 ConfigParser::ParseBool(&bypass);
87 ConfigParser::ParseString(&uri);
88
89 debug(3, 5) ("ICAPService::parseConfigLine (line %d): %s %s %d\n", config_lineno, key.buf(), service_type, bypass);
90
91 method = parseMethod(service_type);
92 point = parseVectPoint(service_type);
93
94 debug(3, 5) ("ICAPService::parseConfigLine (line %d): service is %s_%s\n", config_lineno, methodStr(), vectPointStr());
95
96 if (uri.cmp("icap://", 7) != 0) {
97 debug(3, 0) ("ICAPService::parseConfigLine (line %d): wrong uri: %s\n", config_lineno, uri.buf());
98 return false;
99 }
100
101 const char *s = uri.buf() + 7;
102
103 const char *e;
104
105 bool have_port = false;
106
107 if ((e = strchr(s, ':')) != NULL) {
108 have_port = true;
109 } else if ((e = strchr(s, '/')) != NULL) {
110 have_port = false;
111 } else {
112 return false;
113 }
114
115 int len = e - s;
116 host.limitInit(s, len);
117 s = e;
118
119 if (have_port) {
120 s++;
121
122 if ((e = strchr(s, '/')) != NULL) {
123 char *t;
124 port = strtoul(s, &t, 0) % 65536;
125
126 if (t != e) {
127 return false;
128 }
129
130 s = e;
131
132 if (s[0] != '/') {
133 return false;
134 }
135 }
136 } else {
137
138 struct servent *serv = getservbyname("icap", "tcp");
139
140 if (serv) {
141 port = htons(serv->s_port);
142 } else {
143 port = 1344;
144 }
145 }
146
147 s++;
148 e = strchr(s, '\0');
149 len = e - s;
150
151 if (len > 1024) {
152 debug(3, 0) ("icap_service_process (line %d): long resource name (>1024), probably wrong\n", config_lineno);
153 }
154
155 resource.limitInit(s, len + 1);
156
157 if ((bypass != 0) && (bypass != 1)) {
158 return false;
159 }
160
161 return true;
162
163};
164
165void ICAPServiceRep::invalidate()
166{
167 assert(self != NULL);
c99de607 168 Pointer savedSelf = self; // to prevent destruction when we nullify self
169 self = NULL;
170
171 announceStatusChange("invalidated by reconfigure", false);
172
173 savedSelf = NULL; // may destroy us and, hence, invalidate cbdata(this)
774c051c 174 // TODO: it would be nice to invalidate cbdata(this) when not destroyed
175}
176
c99de607 177void ICAPServiceRep::noteFailure() {
178 ++theSessionFailures;
179 debugs(93,4, "ICAPService failure " << theSessionFailures <<
180 ", out of " << TheSessionFailureLimit << " allowed");
181
182 if (theSessionFailures > TheSessionFailureLimit)
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
220bool ICAPServiceRep::wantsUrl(const String &urlPath) const
221{
222 Must(hasOptions());
223 return theOptions->transferKind(urlPath) != ICAPOptions::xferIgnore;
774c051c 224}
225
c99de607 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{
304 Must(cb);
305 Must(self != NULL);
c99de607 306 Must(!broken()); // we do not wait for a broken service
774c051c 307
308 Client i;
309 i.service = self;
310 i.callback = cb;
311 i.data = cbdataReference(data);
312 theClients.push_back(i);
313
c99de607 314 if (waiting || notifying)
774c051c 315 return; // do nothing, we will be picked up in noteTimeToNotify()
316
317 if (needNewOptions())
318 startGettingOptions();
319 else
320 scheduleNotification();
321}
322
323void ICAPServiceRep::scheduleNotification()
324{
325 debugs(93,7, "ICAPService will notify " << theClients.size() << " clients");
326 eventAdd("ICAPServiceRep::noteTimeToNotify", &ICAPServiceRep_noteTimeToNotify, this, 0, 0, true);
327}
328
774c051c 329bool ICAPServiceRep::needNewOptions() const
330{
c99de607 331 return self != NULL && !up();
774c051c 332}
333
334void ICAPServiceRep::changeOptions(ICAPOptions *newOptions)
335{
336 debugs(93,9, "ICAPService changes options from " << theOptions << " to " <<
337 newOptions);
c99de607 338
774c051c 339 delete theOptions;
340 theOptions = newOptions;
c99de607 341 theSessionFailures = 0;
342 isSuspended = 0;
343 theLastUpdate = squid_curtime;
344
345 checkOptions();
346 announceStatusChange("down after an options fetch failure", true);
347}
ffddf96c 348
c99de607 349void ICAPServiceRep::checkOptions()
350{
9d0cdbb9 351 if (theOptions == NULL)
352 return;
353
ffddf96c 354 /*
eadded2e 355 * Issue a warning if the ICAP server returned methods in the
356 * options response that don't match the method from squid.conf.
ffddf96c 357 */
358
eadded2e 359 if (!theOptions->methods.empty()) {
360 bool method_found = false;
361 String method_list;
362 Vector <ICAP::Method>::iterator iter = theOptions->methods.begin();
eadded2e 363
364 while (iter != theOptions->methods.end()) {
eadded2e 365
366 if (*iter == method) {
367 method_found = true;
368 break;
369 }
370
371 method_list.append(ICAP::methodStr(*iter));
372 method_list.append(" ", 1);
373 iter++;
eadded2e 374 }
375
eadded2e 376 if (!method_found) {
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());
381 }
382 }
8eeb99bf 383
384
385 /*
386 * Check the ICAP server's date header for clock skew
387 */
388 int skew = abs((int)(theOptions->timestamp() - squid_curtime));
8eeb99bf 389 if (skew > theOptions->ttl())
390 debugs(93, 1, host.buf() << "'s clock is skewed by " << skew << " seconds!");
c99de607 391}
8eeb99bf 392
c99de607 393void ICAPServiceRep::announceStatusChange(const char *downPhrase, bool important) const
394{
395 if (wasAnnouncedUp == up()) // no significant changes to announce
396 return;
9d0cdbb9 397
c99de607 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);
9d0cdbb9 402
c99de607 403 wasAnnouncedUp = !wasAnnouncedUp;
774c051c 404}
405
406static
407void ICAPServiceRep_noteNewOptions(ICAPOptXact *x, void *data)
408{
409 ICAPServiceRep *service = static_cast<ICAPServiceRep*>(data);
410 Must(service);
411 service->noteNewOptions(x);
412}
413
414void ICAPServiceRep::noteNewOptions(ICAPOptXact *x)
415{
416 Must(x);
c99de607 417 Must(waiting);
418 waiting = false;
774c051c 419
420 changeOptions(x->options);
421 x->options = NULL;
422 delete x;
423
c99de607 424 debugs(93,3, "ICAPService got new options and is now " << status());
774c051c 425
426 scheduleUpdate();
774c051c 427 scheduleNotification();
428}
429
430void ICAPServiceRep::startGettingOptions()
431{
c99de607 432 Must(!waiting);
774c051c 433 debugs(93,6, "ICAPService will get new options " << status());
c99de607 434 waiting = true;
774c051c 435
436 ICAPOptXact *x = new ICAPOptXact;
437 x->start(self, &ICAPServiceRep_noteNewOptions, this);
c99de607 438 // TODO: timeout in case ICAPOptXact never calls us back?
774c051c 439}
440
441void ICAPServiceRep::scheduleUpdate()
442{
c99de607 443 if (updateScheduled)
444 return; // already scheduled
445
446 // XXX: move hard-coded constants from here to TheICAPConfig
447
448 // conservative estimate of how long the OPTIONS transaction will take
449 const int expectedWait = 20; // seconds
450
451 time_t when = 0;
774c051c 452
453 if (theOptions && theOptions->valid()) {
454 const time_t expire = theOptions->expire();
c99de607 455 debugs(93,7, "ICAPService options expire on " << expire << " >= " << squid_curtime);
774c051c 456
c99de607 457 if (expire < 0) // unknown expiration time
458 when = squid_curtime + 60*60;
774c051c 459 else
c99de607 460 if (expire < expectedWait) // invalid expiration time
461 when = squid_curtime + 60*60;
462 else
463 when = expire - expectedWait; // before the current options expire
774c051c 464 } else {
c99de607 465 when = squid_curtime + 3*60; // delay for a down service
774c051c 466 }
467
c99de607 468 debugs(93,7, "ICAPService options raw update on " << when << " or " << (when - squid_curtime));
469 if (when < squid_curtime)
470 when = squid_curtime;
471
472 const int minUpdateGap = 1*60; // seconds
473 if (when < theLastUpdate + minUpdateGap)
474 when = theLastUpdate + minUpdateGap;
774c051c 475
c99de607 476 // TODO: keep the time of the last update to prevet too-frequent updates
774c051c 477
c99de607 478 const int delay = when - squid_curtime;
479
480 debugs(93,5, "ICAPService will update options in " << delay << " sec");
774c051c 481
482 eventAdd("ICAPServiceRep::noteTimeToUpdate",
483 &ICAPServiceRep_noteTimeToUpdate, this, delay, 0, true);
c99de607 484 updateScheduled = true;
774c051c 485}
486
c99de607 487// returns a temporary string depicting service status, for debugging
774c051c 488const char *ICAPServiceRep::status() const
489{
c99de607 490 static MemBuf buf;
491
492 buf.reset();
493 buf.append("[", 1);
494
495 if (up())
496 buf.append("up", 2);
497 else
498 buf.append("down", 4);
499
774c051c 500 if (!self)
c99de607 501 buf.append(",gone", 5);
774c051c 502
c99de607 503 if (waiting)
504 buf.append(",wait", 5);
774c051c 505
c99de607 506 if (notifying)
507 buf.append(",notif", 6);
774c051c 508
c99de607 509 if (theSessionFailures > 0)
510 buf.Printf(",F%d", theSessionFailures);
774c051c 511
c99de607 512 if (isSuspended)
513 buf.append(",susp", 5);
774c051c 514
c99de607 515 buf.append("]", 1);
516 buf.terminate();
774c051c 517
c99de607 518 return buf.content();
774c051c 519}