2 * DEBUG: section 93 ICAP (RFC 3507) Client
6 #include "adaptation/icap/Config.h"
7 #include "adaptation/icap/ModXact.h"
8 #include "adaptation/icap/Options.h"
9 #include "adaptation/icap/OptXact.h"
10 #include "adaptation/icap/ServiceRep.h"
11 #include "base/TextException.h"
12 #include "ConfigParser.h"
13 #include "HttpReply.h"
14 #include "SquidTime.h"
16 CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap
, ServiceRep
);
18 Adaptation::Icap::ServiceRep::ServiceRep(const Adaptation::ServiceConfig
&svcCfg
):
19 AsyncJob("Adaptation::Icap::ServiceRep"), Adaptation::Service(svcCfg
),
20 theOptions(NULL
), theOptionsFetcher(0), theLastUpdate(0),
21 isSuspended(0), notifying(false),
22 updateScheduled(false),
23 wasAnnouncedUp(true), // do not announce an "up" service at startup
27 Adaptation::Icap::ServiceRep::~ServiceRep()
29 Must(!theOptionsFetcher
);
34 Adaptation::Icap::ServiceRep::finalize()
36 Adaptation::Service::finalize();
38 // use /etc/services or default port if needed
39 const bool have_port
= cfg().port
>= 0;
41 struct servent
*serv
= getservbyname("icap", "tcp");
44 writeableCfg().port
= htons(serv
->s_port
);
46 writeableCfg().port
= 1344;
50 theSessionFailures
.configure(TheConfig
.oldest_service_failure
> 0 ?
51 TheConfig
.oldest_service_failure
: -1);
54 void Adaptation::Icap::ServiceRep::noteFailure()
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());
64 if (TheConfig
.service_failure_limit
>= 0 &&
65 failures
> TheConfig
.service_failure_limit
)
66 suspend("too many failures");
68 // TODO: Should bypass setting affect how much Squid tries to talk to
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
71 // should be configurable.
74 void Adaptation::Icap::ServiceRep::suspend(const char *reason
)
77 debugs(93,4, HERE
<< "keeping suspended, also for " << reason
);
80 debugs(93,1, "suspending ICAP service for " << reason
);
81 scheduleUpdate(squid_curtime
+ TheConfig
.service_revival_delay
);
82 announceStatusChange("suspended", true);
86 bool Adaptation::Icap::ServiceRep::probed() const
88 return theLastUpdate
!= 0;
91 bool Adaptation::Icap::ServiceRep::hasOptions() const
93 return theOptions
&& theOptions
->valid() && theOptions
->fresh();
96 bool Adaptation::Icap::ServiceRep::up() const
98 return !isSuspended
&& hasOptions();
101 bool Adaptation::Icap::ServiceRep::wantsUrl(const String
&urlPath
) const
104 return theOptions
->transferKind(urlPath
) != Adaptation::Icap::Options::xferIgnore
;
107 bool Adaptation::Icap::ServiceRep::wantsPreview(const String
&urlPath
, size_t &wantedSize
) const
111 if (theOptions
->preview
< 0)
114 if (theOptions
->transferKind(urlPath
) != Adaptation::Icap::Options::xferPreview
)
117 wantedSize
= theOptions
->preview
;
122 bool Adaptation::Icap::ServiceRep::allows204() const
125 return true; // in the future, we may have ACLs to prevent 204s
128 bool Adaptation::Icap::ServiceRep::allows206() const
131 if (theOptions
->allow206
)
132 return true; // in the future, we may have ACLs to prevent 206s
138 void ServiceRep_noteTimeToUpdate(void *data
)
140 Adaptation::Icap::ServiceRep
*service
= static_cast<Adaptation::Icap::ServiceRep
*>(data
);
142 service
->noteTimeToUpdate();
145 void Adaptation::Icap::ServiceRep::noteTimeToUpdate()
148 updateScheduled
= false;
150 if (detached() || theOptionsFetcher
.set()) {
151 debugs(93,5, HERE
<< "ignores options update " << status());
155 debugs(93,5, HERE
<< "performs a regular options update " << status());
156 startGettingOptions();
161 void Adaptation::Icap::ServiceRep_noteTimeToNotify(void *data
)
163 Adaptation::Icap::ServiceRep
*service
= static_cast<Adaptation::Icap::ServiceRep
*>(data
);
165 service
->noteTimeToNotify();
169 void Adaptation::Icap::ServiceRep::noteTimeToNotify()
173 debugs(93,7, HERE
<< "notifies " << theClients
.size() << " clients " <<
176 // note: we must notify even if we are invalidated
180 while (!theClients
.empty()) {
181 Client i
= theClients
.pop_back();
182 ScheduleCallHere(i
.callback
);
189 void Adaptation::Icap::ServiceRep::callWhenReady(AsyncCall::Pointer
&cb
)
193 debugs(93,5, HERE
<< "Adaptation::Icap::Service is asked to call " << *cb
<<
194 " when ready " << status());
196 Must(!broken()); // we do not wait for a broken service
199 i
.service
= Pointer(this); // TODO: is this really needed?
201 theClients
.push_back(i
);
203 if (theOptionsFetcher
.set() || notifying
)
204 return; // do nothing, we will be picked up in noteTimeToNotify()
206 if (needNewOptions())
207 startGettingOptions();
209 scheduleNotification();
212 void Adaptation::Icap::ServiceRep::scheduleNotification()
214 debugs(93,7, HERE
<< "will notify " << theClients
.size() << " clients");
215 CallJobHere(93, 5, this, Adaptation::Icap::ServiceRep
, noteTimeToNotify
);
218 bool Adaptation::Icap::ServiceRep::needNewOptions() const
220 return !detached() && !up();
223 void Adaptation::Icap::ServiceRep::changeOptions(Adaptation::Icap::Options
*newOptions
)
225 debugs(93,8, HERE
<< "changes options from " << theOptions
<< " to " <<
226 newOptions
<< ' ' << status());
229 theOptions
= newOptions
;
230 theSessionFailures
.clear();
232 theLastUpdate
= squid_curtime
;
235 announceStatusChange("down after an options fetch failure", true);
238 void Adaptation::Icap::ServiceRep::checkOptions()
240 if (theOptions
== NULL
)
243 if (!theOptions
->valid()) {
244 debugs(93,1, "WARNING: Squid got an invalid ICAP OPTIONS response " <<
245 "from service " << cfg().uri
<< "; error: " << theOptions
->error
);
250 * Issue a warning if the ICAP server returned methods in the
251 * options response that don't match the method from squid.conf.
254 if (!theOptions
->methods
.empty()) {
255 bool method_found
= false;
257 Vector
<ICAP::Method
>::iterator iter
= theOptions
->methods
.begin();
259 while (iter
!= theOptions
->methods
.end()) {
261 if (*iter
== cfg().method
) {
266 method_list
.append(ICAP::methodStr(*iter
));
267 method_list
.append(" ", 1);
272 debugs(93,1, "WARNING: Squid is configured to use ICAP method " <<
274 " for service " << cfg().uri
<<
275 " but OPTIONS response declares the methods are " << method_list
);
281 * Check the ICAP server's date header for clock skew
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
<<
288 " seconds: " << cfg().uri
);
292 void Adaptation::Icap::ServiceRep::announceStatusChange(const char *downPhrase
, bool important
) const
294 if (wasAnnouncedUp
== up()) // no significant changes to announce
297 const char *what
= cfg().bypass
? "optional" : "essential";
298 const char *state
= wasAnnouncedUp
? downPhrase
: "up";
299 const int level
= important
? 1 :2;
300 debugs(93,level
, what
<< " ICAP service is " << state
<< ": " <<
301 cfg().uri
<< ' ' << status());
303 wasAnnouncedUp
= !wasAnnouncedUp
;
306 // we are receiving ICAP OPTIONS response headers here or NULL on failures
307 void Adaptation::Icap::ServiceRep::noteAdaptationAnswer(const Answer
&answer
)
309 Must(initiated(theOptionsFetcher
));
310 clearAdaptation(theOptionsFetcher
);
312 if (answer
.kind
== Answer::akError
) {
313 debugs(93,3, HERE
<< "failed to fetch options " << status());
318 Must(answer
.kind
== Answer::akForward
); // no akBlock for OPTIONS requests
319 HttpMsg
*msg
= answer
.message
;
322 debugs(93,5, HERE
<< "is interpreting new options " << status());
324 Adaptation::Icap::Options
*newOptions
= NULL
;
325 if (HttpReply
*r
= dynamic_cast<HttpReply
*>(msg
)) {
326 newOptions
= new Adaptation::Icap::Options
;
327 newOptions
->configure(r
);
329 debugs(93,1, "ICAP service got wrong options message " << status());
332 handleNewOptions(newOptions
);
335 // we (a) must keep trying to get OPTIONS and (b) are RefCounted so we
336 // must keep our job alive (XXX: until nobody needs us)
337 void Adaptation::Icap::ServiceRep::callException(const std::exception
&e
)
339 clearAdaptation(theOptionsFetcher
);
340 debugs(93,2, "ICAP probably failed to fetch options (" << e
.what() <<
345 void Adaptation::Icap::ServiceRep::handleNewOptions(Adaptation::Icap::Options
*newOptions
)
347 // new options may be NULL
348 changeOptions(newOptions
);
350 debugs(93,3, HERE
<< "got new options and is now " << status());
352 scheduleUpdate(optionsFetchTime());
353 scheduleNotification();
356 void Adaptation::Icap::ServiceRep::startGettingOptions()
358 Must(!theOptionsFetcher
);
359 debugs(93,6, HERE
<< "will get new options " << status());
361 // XXX: "this" here is "self"; works until refcounting API changes
362 theOptionsFetcher
= initiateAdaptation(
363 new Adaptation::Icap::OptXactLauncher(this));
364 // TODO: timeout in case Adaptation::Icap::OptXact never calls us back?
365 // Such a timeout should probably be a generic AsyncStart feature.
368 void Adaptation::Icap::ServiceRep::scheduleUpdate(time_t when
)
370 if (updateScheduled
) {
371 debugs(93,7, HERE
<< "reschedules update");
372 // XXX: check whether the event is there because AR saw
373 // an unreproducible eventDelete assertion on 2007/06/18
374 if (eventFind(&ServiceRep_noteTimeToUpdate
, this))
375 eventDelete(&ServiceRep_noteTimeToUpdate
, this);
377 debugs(93,1, "XXX: ICAP service lost an update event.");
378 updateScheduled
= false;
381 debugs(93,7, HERE
<< "raw OPTIONS fetch at " << when
<< " or in " <<
382 (when
- squid_curtime
) << " sec");
383 debugs(93,9, HERE
<< "last fetched at " << theLastUpdate
<< " or " <<
384 (squid_curtime
- theLastUpdate
) << " sec ago");
386 /* adjust update time to prevent too-frequent updates */
388 if (when
< squid_curtime
)
389 when
= squid_curtime
;
391 // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig
392 const int minUpdateGap
= 30; // seconds
393 if (when
< theLastUpdate
+ minUpdateGap
)
394 when
= theLastUpdate
+ minUpdateGap
;
396 const int delay
= when
- squid_curtime
;
397 debugs(93,5, HERE
<< "will fetch OPTIONS in " << delay
<< " sec");
399 eventAdd("Adaptation::Icap::ServiceRep::noteTimeToUpdate",
400 &ServiceRep_noteTimeToUpdate
, this, delay
, 0, true);
401 updateScheduled
= true;
404 // returns absolute time when OPTIONS should be fetched
406 Adaptation::Icap::ServiceRep::optionsFetchTime() const
408 if (theOptions
&& theOptions
->valid()) {
409 const time_t expire
= theOptions
->expire();
410 debugs(93,7, HERE
<< "options expire on " << expire
<< " >= " << squid_curtime
);
412 // conservative estimate of how long the OPTIONS transaction will take
413 // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig
414 const int expectedWait
= 20; // seconds
416 // Unknown or invalid (too small) expiration times should not happen.
417 // Adaptation::Icap::Options should use the default TTL, and ICAP servers should not
418 // send invalid TTLs, but bugs and attacks happen.
419 if (expire
< expectedWait
)
420 return squid_curtime
;
422 return expire
- expectedWait
; // before the current options expire
425 // use revival delay as "expiration" time for a service w/o valid options
426 return squid_curtime
+ TheConfig
.service_revival_delay
;
429 Adaptation::Initiate
*
430 Adaptation::Icap::ServiceRep::makeXactLauncher(HttpMsg
*virgin
,
433 return new Adaptation::Icap::ModXactLauncher(virgin
, cause
, this);
436 // returns a temporary string depicting service status, for debugging
437 const char *Adaptation::Icap::ServiceRep::status() const
447 buf
.append("down", 4);
449 buf
.append(",susp", 5);
452 buf
.append(",!opt", 5);
453 else if (!theOptions
->valid())
454 buf
.append(",!valid", 7);
455 else if (!theOptions
->fresh())
456 buf
.append(",stale", 6);
460 buf
.append(",detached", 9);
462 if (theOptionsFetcher
.set())
463 buf
.append(",fetch", 6);
466 buf
.append(",notif", 6);
468 if (const int failures
= theSessionFailures
.remembered())
469 buf
.Printf(",fail%d", failures
);
474 return buf
.content();
477 void Adaptation::Icap::ServiceRep::detach()
479 debugs(93,3, HERE
<< "detaching ICAP service: " << cfg().uri
<<
484 bool Adaptation::Icap::ServiceRep::detached() const