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