]>
Commit | Line | Data |
---|---|---|
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" |
26cc52cb AR |
8 | #include "adaptation/icap/ServiceRep.h" |
9 | #include "adaptation/icap/Options.h" | |
10 | #include "adaptation/icap/OptXact.h" | |
774c051c | 11 | #include "ConfigParser.h" |
26cc52cb AR |
12 | #include "adaptation/icap/Config.h" |
13 | #include "adaptation/icap/ModXact.h" | |
985c86bc | 14 | #include "SquidTime.h" |
774c051c | 15 | |
26cc52cb | 16 | CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap, ServiceRep); |
774c051c | 17 | |
26cc52cb AR |
18 | Adaptation::Icap::ServiceRep::ServiceRep(const Adaptation::ServiceConfig &cfg): |
19 | AsyncJob("Adaptation::Icap::ServiceRep"), Adaptation::Service(cfg), | |
98a74c05 | 20 | theOptions(NULL), theOptionsFetcher(0), theLastUpdate(0), |
21 | theSessionFailures(0), isSuspended(0), notifying(false), | |
c99de607 | 22 | updateScheduled(false), self(NULL), |
23 | wasAnnouncedUp(true) // do not announce an "up" service at startup | |
774c051c | 24 | {} |
25 | ||
26cc52cb | 26 | Adaptation::Icap::ServiceRep::~ServiceRep() |
774c051c | 27 | { |
98a74c05 | 28 | Must(!theOptionsFetcher); |
774c051c | 29 | changeOptions(0); |
30 | } | |
31 | ||
62c7f90e | 32 | void |
26cc52cb | 33 | Adaptation::Icap::ServiceRep::setSelf(Pointer &aSelf) |
774c051c | 34 | { |
35 | assert(!self && aSelf != NULL); | |
36 | self = aSelf; | |
62c7f90e | 37 | } |
774c051c | 38 | |
62c7f90e | 39 | void |
26cc52cb | 40 | Adaptation::Icap::ServiceRep::finalize() |
62c7f90e | 41 | { |
9e008dda | 42 | Adaptation::Service::finalize(); |
62c7f90e | 43 | assert(self != NULL); |
774c051c | 44 | |
a68cf076 | 45 | // use /etc/services or default port if needed |
9e008dda | 46 | const bool have_port = cfg().port >= 0; |
a68cf076 | 47 | if (!have_port) { |
774c051c | 48 | struct servent *serv = getservbyname("icap", "tcp"); |
49 | ||
50 | if (serv) { | |
d81a31f1 | 51 | writeableCfg().port = htons(serv->s_port); |
774c051c | 52 | } else { |
d81a31f1 | 53 | writeableCfg().port = 1344; |
774c051c | 54 | } |
55 | } | |
a68cf076 | 56 | } |
774c051c | 57 | |
26cc52cb | 58 | void Adaptation::Icap::ServiceRep::invalidate() |
774c051c | 59 | { |
60 | assert(self != NULL); | |
c99de607 | 61 | Pointer savedSelf = self; // to prevent destruction when we nullify self |
62 | self = NULL; | |
63 | ||
64 | announceStatusChange("invalidated by reconfigure", false); | |
65 | ||
66 | savedSelf = NULL; // may destroy us and, hence, invalidate cbdata(this) | |
774c051c | 67 | // TODO: it would be nice to invalidate cbdata(this) when not destroyed |
68 | } | |
69 | ||
26cc52cb | 70 | void Adaptation::Icap::ServiceRep::noteFailure() |
9e008dda | 71 | { |
c99de607 | 72 | ++theSessionFailures; |
192378eb | 73 | debugs(93,4, HERE << " failure " << theSessionFailures << " out of " << |
26cc52cb | 74 | TheConfig.service_failure_limit << " allowed " << status()); |
1299ecbf | 75 | |
76 | if (isSuspended) | |
77 | return; | |
c99de607 | 78 | |
26cc52cb AR |
79 | if (TheConfig.service_failure_limit >= 0 && |
80 | theSessionFailures > TheConfig.service_failure_limit) | |
c99de607 | 81 | suspend("too many failures"); |
82 | ||
83 | // TODO: Should bypass setting affect how much Squid tries to talk to | |
9e008dda AJ |
84 | // the ICAP service that is currently unusable and is likely to remain |
85 | // so for some time? The current code says "no". Perhaps the answer | |
c99de607 | 86 | // should be configurable. |
87 | } | |
88 | ||
26cc52cb | 89 | void Adaptation::Icap::ServiceRep::suspend(const char *reason) |
9e008dda | 90 | { |
c99de607 | 91 | if (isSuspended) { |
192378eb | 92 | debugs(93,4, HERE << "keeping suspended, also for " << reason); |
c99de607 | 93 | } else { |
94 | isSuspended = reason; | |
192378eb | 95 | debugs(93,1, "suspending ICAP service for " << reason); |
26cc52cb | 96 | scheduleUpdate(squid_curtime + TheConfig.service_revival_delay); |
c99de607 | 97 | announceStatusChange("suspended", true); |
98 | } | |
99 | } | |
100 | ||
26cc52cb | 101 | bool Adaptation::Icap::ServiceRep::probed() const |
c99de607 | 102 | { |
103 | return theLastUpdate != 0; | |
104 | } | |
105 | ||
26cc52cb | 106 | bool Adaptation::Icap::ServiceRep::hasOptions() const |
9e008dda | 107 | { |
c99de607 | 108 | return theOptions && theOptions->valid() && theOptions->fresh(); |
109 | } | |
110 | ||
26cc52cb | 111 | bool Adaptation::Icap::ServiceRep::up() const |
774c051c | 112 | { |
c99de607 | 113 | return self != NULL && !isSuspended && hasOptions(); |
114 | } | |
115 | ||
26cc52cb | 116 | bool Adaptation::Icap::ServiceRep::wantsUrl(const String &urlPath) const |
c99de607 | 117 | { |
118 | Must(hasOptions()); | |
26cc52cb | 119 | return theOptions->transferKind(urlPath) != Adaptation::Icap::Options::xferIgnore; |
774c051c | 120 | } |
121 | ||
26cc52cb | 122 | bool Adaptation::Icap::ServiceRep::wantsPreview(const String &urlPath, size_t &wantedSize) const |
774c051c | 123 | { |
c99de607 | 124 | Must(hasOptions()); |
774c051c | 125 | |
126 | if (theOptions->preview < 0) | |
127 | return false; | |
128 | ||
26cc52cb | 129 | if (theOptions->transferKind(urlPath) != Adaptation::Icap::Options::xferPreview) |
c99de607 | 130 | return false; |
131 | ||
774c051c | 132 | wantedSize = theOptions->preview; |
133 | ||
134 | return true; | |
135 | } | |
136 | ||
26cc52cb | 137 | bool Adaptation::Icap::ServiceRep::allows204() const |
774c051c | 138 | { |
c99de607 | 139 | Must(hasOptions()); |
774c051c | 140 | return true; // in the future, we may have ACLs to prevent 204s |
141 | } | |
142 | ||
143 | ||
144 | static | |
26cc52cb | 145 | void ServiceRep_noteTimeToUpdate(void *data) |
774c051c | 146 | { |
26cc52cb | 147 | Adaptation::Icap::ServiceRep *service = static_cast<Adaptation::Icap::ServiceRep*>(data); |
774c051c | 148 | Must(service); |
149 | service->noteTimeToUpdate(); | |
150 | } | |
151 | ||
26cc52cb | 152 | void Adaptation::Icap::ServiceRep::noteTimeToUpdate() |
774c051c | 153 | { |
c99de607 | 154 | if (self != NULL) |
155 | updateScheduled = false; | |
156 | ||
98a74c05 | 157 | if (!self || theOptionsFetcher) { |
192378eb | 158 | debugs(93,5, HERE << "ignores options update " << status()); |
774c051c | 159 | return; |
160 | } | |
161 | ||
192378eb | 162 | debugs(93,5, HERE << "performs a regular options update " << status()); |
774c051c | 163 | startGettingOptions(); |
164 | } | |
165 | ||
bd7f2ede | 166 | #if 0 |
774c051c | 167 | static |
26cc52cb | 168 | void Adaptation::Icap::ServiceRep_noteTimeToNotify(void *data) |
774c051c | 169 | { |
26cc52cb | 170 | Adaptation::Icap::ServiceRep *service = static_cast<Adaptation::Icap::ServiceRep*>(data); |
774c051c | 171 | Must(service); |
172 | service->noteTimeToNotify(); | |
173 | } | |
bd7f2ede | 174 | #endif |
774c051c | 175 | |
26cc52cb | 176 | void Adaptation::Icap::ServiceRep::noteTimeToNotify() |
774c051c | 177 | { |
178 | Must(!notifying); | |
179 | notifying = true; | |
192378eb | 180 | debugs(93,7, HERE << "notifies " << theClients.size() << " clients " << |
774c051c | 181 | status()); |
182 | ||
183 | // note: we must notify even if we are invalidated | |
184 | ||
185 | Pointer us = NULL; | |
186 | ||
187 | while (!theClients.empty()) { | |
188 | Client i = theClients.pop_back(); | |
9e008dda AJ |
189 | ScheduleCallHere(i.callback); |
190 | i.callback = 0; | |
774c051c | 191 | } |
192 | ||
193 | notifying = false; | |
194 | } | |
195 | ||
26cc52cb | 196 | void Adaptation::Icap::ServiceRep::callWhenReady(AsyncCall::Pointer &cb) |
774c051c | 197 | { |
bd7f2ede | 198 | Must(cb!=NULL); |
199 | ||
26cc52cb | 200 | debugs(93,5, HERE << "Adaptation::Icap::Service is asked to call " << *cb << |
9e008dda | 201 | " when ready " << status()); |
5f8252d2 | 202 | |
774c051c | 203 | Must(self != NULL); |
c99de607 | 204 | Must(!broken()); // we do not wait for a broken service |
774c051c | 205 | |
206 | Client i; | |
bd7f2ede | 207 | i.service = self; // TODO: is this really needed? |
774c051c | 208 | i.callback = cb; |
774c051c | 209 | theClients.push_back(i); |
210 | ||
98a74c05 | 211 | if (theOptionsFetcher || notifying) |
774c051c | 212 | return; // do nothing, we will be picked up in noteTimeToNotify() |
213 | ||
214 | if (needNewOptions()) | |
215 | startGettingOptions(); | |
216 | else | |
217 | scheduleNotification(); | |
218 | } | |
219 | ||
26cc52cb | 220 | void Adaptation::Icap::ServiceRep::scheduleNotification() |
774c051c | 221 | { |
192378eb | 222 | debugs(93,7, HERE << "will notify " << theClients.size() << " clients"); |
26cc52cb | 223 | CallJobHere(93, 5, this, Adaptation::Icap::ServiceRep::noteTimeToNotify); |
774c051c | 224 | } |
225 | ||
26cc52cb | 226 | bool Adaptation::Icap::ServiceRep::needNewOptions() const |
774c051c | 227 | { |
c99de607 | 228 | return self != NULL && !up(); |
774c051c | 229 | } |
230 | ||
26cc52cb | 231 | void Adaptation::Icap::ServiceRep::changeOptions(Adaptation::Icap::Options *newOptions) |
774c051c | 232 | { |
192378eb | 233 | debugs(93,8, HERE << "changes options from " << theOptions << " to " << |
5f8252d2 | 234 | newOptions << ' ' << status()); |
c99de607 | 235 | |
774c051c | 236 | delete theOptions; |
237 | theOptions = newOptions; | |
c99de607 | 238 | theSessionFailures = 0; |
239 | isSuspended = 0; | |
240 | theLastUpdate = squid_curtime; | |
241 | ||
242 | checkOptions(); | |
243 | announceStatusChange("down after an options fetch failure", true); | |
244 | } | |
ffddf96c | 245 | |
26cc52cb | 246 | void Adaptation::Icap::ServiceRep::checkOptions() |
c99de607 | 247 | { |
9d0cdbb9 | 248 | if (theOptions == NULL) |
249 | return; | |
250 | ||
eafe3f72 | 251 | if (!theOptions->valid()) { |
252 | debugs(93,1, "WARNING: Squid got an invalid ICAP OPTIONS response " << | |
9e008dda | 253 | "from service " << cfg().uri << "; error: " << theOptions->error); |
eafe3f72 | 254 | return; |
255 | } | |
256 | ||
ffddf96c | 257 | /* |
eadded2e | 258 | * Issue a warning if the ICAP server returned methods in the |
259 | * options response that don't match the method from squid.conf. | |
ffddf96c | 260 | */ |
261 | ||
eadded2e | 262 | if (!theOptions->methods.empty()) { |
263 | bool method_found = false; | |
30abd221 | 264 | String method_list; |
eadded2e | 265 | Vector <ICAP::Method>::iterator iter = theOptions->methods.begin(); |
eadded2e | 266 | |
267 | while (iter != theOptions->methods.end()) { | |
eadded2e | 268 | |
d81a31f1 | 269 | if (*iter == cfg().method) { |
eadded2e | 270 | method_found = true; |
271 | break; | |
272 | } | |
273 | ||
274 | method_list.append(ICAP::methodStr(*iter)); | |
275 | method_list.append(" ", 1); | |
276 | iter++; | |
eadded2e | 277 | } |
278 | ||
eadded2e | 279 | if (!method_found) { |
280 | debugs(93,1, "WARNING: Squid is configured to use ICAP method " << | |
d81a31f1 | 281 | cfg().methodStr() << |
a7a42b14 | 282 | " for service " << cfg().uri << |
5b4117d8 | 283 | " but OPTIONS response declares the methods are " << method_list); |
eadded2e | 284 | } |
285 | } | |
8eeb99bf | 286 | |
287 | ||
288 | /* | |
289 | * Check the ICAP server's date header for clock skew | |
290 | */ | |
5f8252d2 | 291 | const int skew = (int)(theOptions->timestamp() - squid_curtime); |
292 | if (abs(skew) > theOptions->ttl()) { | |
293 | // TODO: If skew is negative, the option will be considered down | |
294 | // because of stale options. We should probably change this. | |
295 | debugs(93, 1, "ICAP service's clock is skewed by " << skew << | |
a7a42b14 | 296 | " seconds: " << cfg().uri); |
5f8252d2 | 297 | } |
c99de607 | 298 | } |
8eeb99bf | 299 | |
26cc52cb | 300 | void Adaptation::Icap::ServiceRep::announceStatusChange(const char *downPhrase, bool important) const |
c99de607 | 301 | { |
302 | if (wasAnnouncedUp == up()) // no significant changes to announce | |
303 | return; | |
9d0cdbb9 | 304 | |
d81a31f1 | 305 | const char *what = cfg().bypass ? "optional" : "essential"; |
c99de607 | 306 | const char *state = wasAnnouncedUp ? downPhrase : "up"; |
9e008dda | 307 | const int level = important ? 1 :2; |
d81a31f1 | 308 | debugs(93,level, what << " ICAP service is " << state << ": " << |
9e008dda | 309 | cfg().uri << ' ' << status()); |
9d0cdbb9 | 310 | |
c99de607 | 311 | wasAnnouncedUp = !wasAnnouncedUp; |
774c051c | 312 | } |
313 | ||
c824c43b | 314 | // we are receiving ICAP OPTIONS response headers here or NULL on failures |
26cc52cb | 315 | void Adaptation::Icap::ServiceRep::noteAdaptationAnswer(HttpMsg *msg) |
774c051c | 316 | { |
98a74c05 | 317 | Must(theOptionsFetcher); |
d81a31f1 | 318 | clearAdaptation(theOptionsFetcher); |
774c051c | 319 | |
c824c43b | 320 | Must(msg); |
321 | ||
192378eb | 322 | debugs(93,5, HERE << "is interpreting new options " << status()); |
5f8252d2 | 323 | |
26cc52cb | 324 | Adaptation::Icap::Options *newOptions = NULL; |
c824c43b | 325 | if (HttpReply *r = dynamic_cast<HttpReply*>(msg)) { |
26cc52cb | 326 | newOptions = new Adaptation::Icap::Options; |
9e008dda | 327 | newOptions->configure(r); |
c824c43b | 328 | } else { |
192378eb | 329 | debugs(93,1, "ICAP service got wrong options message " << status()); |
c824c43b | 330 | } |
331 | ||
332 | handleNewOptions(newOptions); | |
333 | } | |
334 | ||
26cc52cb | 335 | void Adaptation::Icap::ServiceRep::noteAdaptationQueryAbort(bool) |
9e008dda | 336 | { |
98a74c05 | 337 | Must(theOptionsFetcher); |
d81a31f1 | 338 | clearAdaptation(theOptionsFetcher); |
774c051c | 339 | |
192378eb | 340 | debugs(93,3, HERE << "failed to fetch options " << status()); |
c824c43b | 341 | handleNewOptions(0); |
342 | } | |
343 | ||
26cc52cb | 344 | void Adaptation::Icap::ServiceRep::handleNewOptions(Adaptation::Icap::Options *newOptions) |
c824c43b | 345 | { |
346 | // new options may be NULL | |
5f8252d2 | 347 | changeOptions(newOptions); |
774c051c | 348 | |
192378eb | 349 | debugs(93,3, HERE << "got new options and is now " << status()); |
774c051c | 350 | |
1299ecbf | 351 | scheduleUpdate(optionsFetchTime()); |
774c051c | 352 | scheduleNotification(); |
353 | } | |
354 | ||
26cc52cb | 355 | void Adaptation::Icap::ServiceRep::startGettingOptions() |
774c051c | 356 | { |
98a74c05 | 357 | Must(!theOptionsFetcher); |
192378eb | 358 | debugs(93,6, HERE << "will get new options " << status()); |
774c051c | 359 | |
68854a41 | 360 | // XXX: second "this" is "self"; this works but may stop if API changes |
26cc52cb | 361 | theOptionsFetcher = initiateAdaptation(new Adaptation::Icap::OptXactLauncher(this, this)); |
bd7f2ede | 362 | Must(theOptionsFetcher); |
26cc52cb | 363 | // TODO: timeout in case Adaptation::Icap::OptXact never calls us back? |
c824c43b | 364 | // Such a timeout should probably be a generic AsyncStart feature. |
774c051c | 365 | } |
366 | ||
26cc52cb | 367 | void Adaptation::Icap::ServiceRep::scheduleUpdate(time_t when) |
774c051c | 368 | { |
1299ecbf | 369 | if (updateScheduled) { |
192378eb | 370 | debugs(93,7, HERE << "reschedules update"); |
1299ecbf | 371 | // XXX: check whether the event is there because AR saw |
04d76948 | 372 | // an unreproducible eventDelete assertion on 2007/06/18 |
26cc52cb AR |
373 | if (eventFind(&ServiceRep_noteTimeToUpdate, this)) |
374 | eventDelete(&ServiceRep_noteTimeToUpdate, this); | |
c99de607 | 375 | else |
192378eb | 376 | debugs(93,1, "XXX: ICAP service lost an update event."); |
1299ecbf | 377 | updateScheduled = false; |
774c051c | 378 | } |
379 | ||
1299ecbf | 380 | debugs(93,7, HERE << "raw OPTIONS fetch at " << when << " or in " << |
9e008dda | 381 | (when - squid_curtime) << " sec"); |
1299ecbf | 382 | debugs(93,9, HERE << "last fetched at " << theLastUpdate << " or " << |
9e008dda | 383 | (squid_curtime - theLastUpdate) << " sec ago"); |
5f8252d2 | 384 | |
385 | /* adjust update time to prevent too-frequent updates */ | |
386 | ||
c99de607 | 387 | if (when < squid_curtime) |
388 | when = squid_curtime; | |
389 | ||
26cc52cb | 390 | // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig |
1299ecbf | 391 | const int minUpdateGap = 30; // seconds |
c99de607 | 392 | if (when < theLastUpdate + minUpdateGap) |
393 | when = theLastUpdate + minUpdateGap; | |
774c051c | 394 | |
c99de607 | 395 | const int delay = when - squid_curtime; |
192378eb | 396 | debugs(93,5, HERE << "will fetch OPTIONS in " << delay << " sec"); |
1299ecbf | 397 | |
26cc52cb AR |
398 | eventAdd("Adaptation::Icap::ServiceRep::noteTimeToUpdate", |
399 | &ServiceRep_noteTimeToUpdate, this, delay, 0, true); | |
c99de607 | 400 | updateScheduled = true; |
774c051c | 401 | } |
402 | ||
1299ecbf | 403 | // returns absolute time when OPTIONS should be fetched |
404 | time_t | |
26cc52cb | 405 | Adaptation::Icap::ServiceRep::optionsFetchTime() const |
1299ecbf | 406 | { |
407 | if (theOptions && theOptions->valid()) { | |
408 | const time_t expire = theOptions->expire(); | |
192378eb | 409 | debugs(93,7, HERE << "options expire on " << expire << " >= " << squid_curtime); |
1299ecbf | 410 | |
411 | // conservative estimate of how long the OPTIONS transaction will take | |
26cc52cb | 412 | // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig |
1299ecbf | 413 | const int expectedWait = 20; // seconds |
414 | ||
415 | // Unknown or invalid (too small) expiration times should not happen. | |
26cc52cb | 416 | // Adaptation::Icap::Options should use the default TTL, and ICAP servers should not |
1299ecbf | 417 | // send invalid TTLs, but bugs and attacks happen. |
418 | if (expire < expectedWait) | |
419 | return squid_curtime; | |
420 | else | |
421 | return expire - expectedWait; // before the current options expire | |
422 | } | |
423 | ||
424 | // use revival delay as "expiration" time for a service w/o valid options | |
26cc52cb | 425 | return squid_curtime + TheConfig.service_revival_delay; |
1299ecbf | 426 | } |
427 | ||
d81a31f1 | 428 | Adaptation::Initiate * |
26cc52cb | 429 | Adaptation::Icap::ServiceRep::makeXactLauncher(Adaptation::Initiator *initiator, |
af6a12ee | 430 | HttpMsg *virgin, HttpRequest *cause) |
d81a31f1 | 431 | { |
26cc52cb | 432 | return new Adaptation::Icap::ModXactLauncher(initiator, virgin, cause, this); |
d81a31f1 AR |
433 | } |
434 | ||
c99de607 | 435 | // returns a temporary string depicting service status, for debugging |
26cc52cb | 436 | const char *Adaptation::Icap::ServiceRep::status() const |
774c051c | 437 | { |
c99de607 | 438 | static MemBuf buf; |
439 | ||
440 | buf.reset(); | |
441 | buf.append("[", 1); | |
442 | ||
443 | if (up()) | |
444 | buf.append("up", 2); | |
5f8252d2 | 445 | else { |
c99de607 | 446 | buf.append("down", 4); |
5f8252d2 | 447 | if (!self) |
448 | buf.append(",gone", 5); | |
449 | if (isSuspended) | |
450 | buf.append(",susp", 5); | |
c99de607 | 451 | |
5f8252d2 | 452 | if (!theOptions) |
453 | buf.append(",!opt", 5); | |
e1381638 AJ |
454 | else if (!theOptions->valid()) |
455 | buf.append(",!valid", 7); | |
456 | else if (!theOptions->fresh()) | |
457 | buf.append(",stale", 6); | |
5f8252d2 | 458 | } |
774c051c | 459 | |
98a74c05 | 460 | if (theOptionsFetcher) |
461 | buf.append(",fetch", 6); | |
774c051c | 462 | |
c99de607 | 463 | if (notifying) |
464 | buf.append(",notif", 6); | |
774c051c | 465 | |
c99de607 | 466 | if (theSessionFailures > 0) |
5f8252d2 | 467 | buf.Printf(",fail%d", theSessionFailures); |
774c051c | 468 | |
c99de607 | 469 | buf.append("]", 1); |
470 | buf.terminate(); | |
774c051c | 471 | |
c99de607 | 472 | return buf.content(); |
774c051c | 473 | } |