2 * DEBUG: section 93 ICAP (RFC 3507) Client
6 #include "TextException.h"
8 #include "adaptation/icap/ServiceRep.h"
9 #include "adaptation/icap/Options.h"
10 #include "adaptation/icap/OptXact.h"
11 #include "ConfigParser.h"
12 #include "adaptation/icap/Config.h"
13 #include "adaptation/icap/ModXact.h"
14 #include "SquidTime.h"
16 CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap
, ServiceRep
);
18 Adaptation::Icap::ServiceRep::ServiceRep(const Adaptation::ServiceConfig
&cfg
):
19 AsyncJob("Adaptation::Icap::ServiceRep"), Adaptation::Service(cfg
),
20 theOptions(NULL
), theOptionsFetcher(0), theLastUpdate(0),
21 theSessionFailures(0), isSuspended(0), notifying(false),
22 updateScheduled(false), self(NULL
),
23 wasAnnouncedUp(true) // do not announce an "up" service at startup
26 Adaptation::Icap::ServiceRep::~ServiceRep()
28 Must(!theOptionsFetcher
);
33 Adaptation::Icap::ServiceRep::setSelf(Pointer
&aSelf
)
35 assert(!self
&& aSelf
!= NULL
);
40 Adaptation::Icap::ServiceRep::finalize()
42 Adaptation::Service::finalize();
45 // use /etc/services or default port if needed
46 const bool have_port
= cfg().port
>= 0;
48 struct servent
*serv
= getservbyname("icap", "tcp");
51 writeableCfg().port
= htons(serv
->s_port
);
53 writeableCfg().port
= 1344;
58 void Adaptation::Icap::ServiceRep::invalidate()
61 Pointer savedSelf
= self
; // to prevent destruction when we nullify self
64 announceStatusChange("invalidated by reconfigure", false);
66 savedSelf
= NULL
; // may destroy us and, hence, invalidate cbdata(this)
67 // TODO: it would be nice to invalidate cbdata(this) when not destroyed
70 void Adaptation::Icap::ServiceRep::noteFailure()
73 debugs(93,4, HERE
<< " failure " << theSessionFailures
<< " out of " <<
74 TheConfig
.service_failure_limit
<< " allowed " << status());
79 if (TheConfig
.service_failure_limit
>= 0 &&
80 theSessionFailures
> TheConfig
.service_failure_limit
)
81 suspend("too many failures");
83 // TODO: Should bypass setting affect how much Squid tries to talk to
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
86 // should be configurable.
89 void Adaptation::Icap::ServiceRep::suspend(const char *reason
)
92 debugs(93,4, HERE
<< "keeping suspended, also for " << reason
);
95 debugs(93,1, "suspending ICAP service for " << reason
);
96 scheduleUpdate(squid_curtime
+ TheConfig
.service_revival_delay
);
97 announceStatusChange("suspended", true);
101 bool Adaptation::Icap::ServiceRep::probed() const
103 return theLastUpdate
!= 0;
106 bool Adaptation::Icap::ServiceRep::hasOptions() const
108 return theOptions
&& theOptions
->valid() && theOptions
->fresh();
111 bool Adaptation::Icap::ServiceRep::up() const
113 return self
!= NULL
&& !isSuspended
&& hasOptions();
116 bool Adaptation::Icap::ServiceRep::wantsUrl(const String
&urlPath
) const
119 return theOptions
->transferKind(urlPath
) != Adaptation::Icap::Options::xferIgnore
;
122 bool Adaptation::Icap::ServiceRep::wantsPreview(const String
&urlPath
, size_t &wantedSize
) const
126 if (theOptions
->preview
< 0)
129 if (theOptions
->transferKind(urlPath
) != Adaptation::Icap::Options::xferPreview
)
132 wantedSize
= theOptions
->preview
;
137 bool Adaptation::Icap::ServiceRep::allows204() const
140 return true; // in the future, we may have ACLs to prevent 204s
145 void ServiceRep_noteTimeToUpdate(void *data
)
147 Adaptation::Icap::ServiceRep
*service
= static_cast<Adaptation::Icap::ServiceRep
*>(data
);
149 service
->noteTimeToUpdate();
152 void Adaptation::Icap::ServiceRep::noteTimeToUpdate()
155 updateScheduled
= false;
157 if (!self
|| theOptionsFetcher
) {
158 debugs(93,5, HERE
<< "ignores options update " << status());
162 debugs(93,5, HERE
<< "performs a regular options update " << status());
163 startGettingOptions();
168 void Adaptation::Icap::ServiceRep_noteTimeToNotify(void *data
)
170 Adaptation::Icap::ServiceRep
*service
= static_cast<Adaptation::Icap::ServiceRep
*>(data
);
172 service
->noteTimeToNotify();
176 void Adaptation::Icap::ServiceRep::noteTimeToNotify()
180 debugs(93,7, HERE
<< "notifies " << theClients
.size() << " clients " <<
183 // note: we must notify even if we are invalidated
187 while (!theClients
.empty()) {
188 Client i
= theClients
.pop_back();
189 ScheduleCallHere(i
.callback
);
196 void Adaptation::Icap::ServiceRep::callWhenReady(AsyncCall::Pointer
&cb
)
200 debugs(93,5, HERE
<< "Adaptation::Icap::Service is asked to call " << *cb
<<
201 " when ready " << status());
204 Must(!broken()); // we do not wait for a broken service
207 i
.service
= self
; // TODO: is this really needed?
209 theClients
.push_back(i
);
211 if (theOptionsFetcher
|| notifying
)
212 return; // do nothing, we will be picked up in noteTimeToNotify()
214 if (needNewOptions())
215 startGettingOptions();
217 scheduleNotification();
220 void Adaptation::Icap::ServiceRep::scheduleNotification()
222 debugs(93,7, HERE
<< "will notify " << theClients
.size() << " clients");
223 CallJobHere(93, 5, this, Adaptation::Icap::ServiceRep::noteTimeToNotify
);
226 bool Adaptation::Icap::ServiceRep::needNewOptions() const
228 return self
!= NULL
&& !up();
231 void Adaptation::Icap::ServiceRep::changeOptions(Adaptation::Icap::Options
*newOptions
)
233 debugs(93,8, HERE
<< "changes options from " << theOptions
<< " to " <<
234 newOptions
<< ' ' << status());
237 theOptions
= newOptions
;
238 theSessionFailures
= 0;
240 theLastUpdate
= squid_curtime
;
243 announceStatusChange("down after an options fetch failure", true);
246 void Adaptation::Icap::ServiceRep::checkOptions()
248 if (theOptions
== NULL
)
251 if (!theOptions
->valid()) {
252 debugs(93,1, "WARNING: Squid got an invalid ICAP OPTIONS response " <<
253 "from service " << cfg().uri
<< "; error: " << theOptions
->error
);
258 * Issue a warning if the ICAP server returned methods in the
259 * options response that don't match the method from squid.conf.
262 if (!theOptions
->methods
.empty()) {
263 bool method_found
= false;
265 Vector
<ICAP::Method
>::iterator iter
= theOptions
->methods
.begin();
267 while (iter
!= theOptions
->methods
.end()) {
269 if (*iter
== cfg().method
) {
274 method_list
.append(ICAP::methodStr(*iter
));
275 method_list
.append(" ", 1);
280 debugs(93,1, "WARNING: Squid is configured to use ICAP method " <<
282 " for service " << cfg().uri
<<
283 " but OPTIONS response declares the methods are " << method_list
);
289 * Check the ICAP server's date header for clock skew
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
<<
296 " seconds: " << cfg().uri
);
300 void Adaptation::Icap::ServiceRep::announceStatusChange(const char *downPhrase
, bool important
) const
302 if (wasAnnouncedUp
== up()) // no significant changes to announce
305 const char *what
= cfg().bypass
? "optional" : "essential";
306 const char *state
= wasAnnouncedUp
? downPhrase
: "up";
307 const int level
= important
? 1 :2;
308 debugs(93,level
, what
<< " ICAP service is " << state
<< ": " <<
309 cfg().uri
<< ' ' << status());
311 wasAnnouncedUp
= !wasAnnouncedUp
;
314 // we are receiving ICAP OPTIONS response headers here or NULL on failures
315 void Adaptation::Icap::ServiceRep::noteAdaptationAnswer(HttpMsg
*msg
)
317 Must(theOptionsFetcher
);
318 clearAdaptation(theOptionsFetcher
);
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 void Adaptation::Icap::ServiceRep::noteAdaptationQueryAbort(bool)
337 Must(theOptionsFetcher
);
338 clearAdaptation(theOptionsFetcher
);
340 debugs(93,3, HERE
<< "failed to fetch options " << status());
344 void Adaptation::Icap::ServiceRep::handleNewOptions(Adaptation::Icap::Options
*newOptions
)
346 // new options may be NULL
347 changeOptions(newOptions
);
349 debugs(93,3, HERE
<< "got new options and is now " << status());
351 scheduleUpdate(optionsFetchTime());
352 scheduleNotification();
355 void Adaptation::Icap::ServiceRep::startGettingOptions()
357 Must(!theOptionsFetcher
);
358 debugs(93,6, HERE
<< "will get new options " << status());
360 // XXX: second "this" is "self"; this works but may stop if API changes
361 theOptionsFetcher
= initiateAdaptation(new Adaptation::Icap::OptXactLauncher(this, this));
362 Must(theOptionsFetcher
);
363 // TODO: timeout in case Adaptation::Icap::OptXact never calls us back?
364 // Such a timeout should probably be a generic AsyncStart feature.
367 void Adaptation::Icap::ServiceRep::scheduleUpdate(time_t when
)
369 if (updateScheduled
) {
370 debugs(93,7, HERE
<< "reschedules update");
371 // XXX: check whether the event is there because AR saw
372 // an unreproducible eventDelete assertion on 2007/06/18
373 if (eventFind(&ServiceRep_noteTimeToUpdate
, this))
374 eventDelete(&ServiceRep_noteTimeToUpdate
, this);
376 debugs(93,1, "XXX: ICAP service lost an update event.");
377 updateScheduled
= false;
380 debugs(93,7, HERE
<< "raw OPTIONS fetch at " << when
<< " or in " <<
381 (when
- squid_curtime
) << " sec");
382 debugs(93,9, HERE
<< "last fetched at " << theLastUpdate
<< " or " <<
383 (squid_curtime
- theLastUpdate
) << " sec ago");
385 /* adjust update time to prevent too-frequent updates */
387 if (when
< squid_curtime
)
388 when
= squid_curtime
;
390 // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig
391 const int minUpdateGap
= 30; // seconds
392 if (when
< theLastUpdate
+ minUpdateGap
)
393 when
= theLastUpdate
+ minUpdateGap
;
395 const int delay
= when
- squid_curtime
;
396 debugs(93,5, HERE
<< "will fetch OPTIONS in " << delay
<< " sec");
398 eventAdd("Adaptation::Icap::ServiceRep::noteTimeToUpdate",
399 &ServiceRep_noteTimeToUpdate
, this, delay
, 0, true);
400 updateScheduled
= true;
403 // returns absolute time when OPTIONS should be fetched
405 Adaptation::Icap::ServiceRep::optionsFetchTime() const
407 if (theOptions
&& theOptions
->valid()) {
408 const time_t expire
= theOptions
->expire();
409 debugs(93,7, HERE
<< "options expire on " << expire
<< " >= " << squid_curtime
);
411 // conservative estimate of how long the OPTIONS transaction will take
412 // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig
413 const int expectedWait
= 20; // seconds
415 // Unknown or invalid (too small) expiration times should not happen.
416 // Adaptation::Icap::Options should use the default TTL, and ICAP servers should not
417 // send invalid TTLs, but bugs and attacks happen.
418 if (expire
< expectedWait
)
419 return squid_curtime
;
421 return expire
- expectedWait
; // before the current options expire
424 // use revival delay as "expiration" time for a service w/o valid options
425 return squid_curtime
+ TheConfig
.service_revival_delay
;
428 Adaptation::Initiate
*
429 Adaptation::Icap::ServiceRep::makeXactLauncher(Adaptation::Initiator
*initiator
,
430 HttpMsg
*virgin
, HttpRequest
*cause
)
432 return new Adaptation::Icap::ModXactLauncher(initiator
, virgin
, cause
, this);
435 // returns a temporary string depicting service status, for debugging
436 const char *Adaptation::Icap::ServiceRep::status() const
446 buf
.append("down", 4);
448 buf
.append(",gone", 5);
450 buf
.append(",susp", 5);
453 buf
.append(",!opt", 5);
455 if (!theOptions
->valid())
456 buf
.append(",!valid", 7);
458 if (!theOptions
->fresh())
459 buf
.append(",stale", 6);
462 if (theOptionsFetcher
)
463 buf
.append(",fetch", 6);
466 buf
.append(",notif", 6);
468 if (theSessionFailures
> 0)
469 buf
.Printf(",fail%d", theSessionFailures
);
474 return buf
.content();