]>
Commit | Line | Data |
---|---|---|
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" | |
11 | ||
12 | CBDATA_CLASS_INIT(ICAPServiceRep); | |
13 | ||
14 | ICAPServiceRep::ICAPServiceRep(): method(ICAP::methodNone), | |
15 | point(ICAP::pointNone), port(-1), bypass(false), unreachable(false), | |
16 | theOptions(NULL), theState(stateInit), notifying(false), self(NULL) | |
17 | {} | |
18 | ||
19 | ICAPServiceRep::~ICAPServiceRep() | |
20 | { | |
21 | Must(!waiting()); | |
22 | changeOptions(0); | |
23 | } | |
24 | ||
25 | const char * | |
26 | ICAPServiceRep::methodStr() const | |
27 | { | |
28 | return ICAP::methodStr(method); | |
29 | } | |
30 | ||
31 | ICAP::Method | |
32 | ICAPServiceRep::parseMethod(const char *str) const | |
33 | { | |
34 | if (!strncasecmp(str, "REQMOD", 6)) | |
35 | return ICAP::methodReqmod; | |
36 | ||
37 | if (!strncasecmp(str, "RESPMOD", 7)) | |
38 | return ICAP::methodRespmod; | |
39 | ||
40 | return ICAP::methodNone; | |
41 | } | |
42 | ||
43 | ||
44 | const char * | |
45 | ICAPServiceRep::vectPointStr() const | |
46 | { | |
47 | return ICAP::vectPointStr(point); | |
48 | } | |
49 | ||
50 | ICAP::VectPoint | |
51 | ICAPServiceRep::parseVectPoint(const char *service) const | |
52 | { | |
53 | const char *t = service; | |
54 | const char *q = strchr(t, '_'); | |
55 | ||
56 | if (q) | |
57 | t = q + 1; | |
58 | ||
59 | if (!strcasecmp(t, "precache")) | |
60 | return ICAP::pointPreCache; | |
61 | ||
62 | if (!strcasecmp(t, "postcache")) | |
63 | return ICAP::pointPostCache; | |
64 | ||
65 | return ICAP::pointNone; | |
66 | } | |
67 | ||
68 | bool | |
69 | ICAPServiceRep::configure(Pointer &aSelf) | |
70 | { | |
71 | assert(!self && aSelf != NULL); | |
72 | self = aSelf; | |
73 | ||
74 | char *service_type = NULL; | |
75 | ||
76 | ConfigParser::ParseString(&key); | |
77 | ConfigParser::ParseString(&service_type); | |
78 | ConfigParser::ParseBool(&bypass); | |
79 | ConfigParser::ParseString(&uri); | |
80 | ||
81 | debug(3, 5) ("ICAPService::parseConfigLine (line %d): %s %s %d\n", config_lineno, key.buf(), service_type, bypass); | |
82 | ||
83 | method = parseMethod(service_type); | |
84 | point = parseVectPoint(service_type); | |
85 | ||
86 | debug(3, 5) ("ICAPService::parseConfigLine (line %d): service is %s_%s\n", config_lineno, methodStr(), vectPointStr()); | |
87 | ||
88 | if (uri.cmp("icap://", 7) != 0) { | |
89 | debug(3, 0) ("ICAPService::parseConfigLine (line %d): wrong uri: %s\n", config_lineno, uri.buf()); | |
90 | return false; | |
91 | } | |
92 | ||
93 | const char *s = uri.buf() + 7; | |
94 | ||
95 | const char *e; | |
96 | ||
97 | bool have_port = false; | |
98 | ||
99 | if ((e = strchr(s, ':')) != NULL) { | |
100 | have_port = true; | |
101 | } else if ((e = strchr(s, '/')) != NULL) { | |
102 | have_port = false; | |
103 | } else { | |
104 | return false; | |
105 | } | |
106 | ||
107 | int len = e - s; | |
108 | host.limitInit(s, len); | |
109 | s = e; | |
110 | ||
111 | if (have_port) { | |
112 | s++; | |
113 | ||
114 | if ((e = strchr(s, '/')) != NULL) { | |
115 | char *t; | |
116 | port = strtoul(s, &t, 0) % 65536; | |
117 | ||
118 | if (t != e) { | |
119 | return false; | |
120 | } | |
121 | ||
122 | s = e; | |
123 | ||
124 | if (s[0] != '/') { | |
125 | return false; | |
126 | } | |
127 | } | |
128 | } else { | |
129 | ||
130 | struct servent *serv = getservbyname("icap", "tcp"); | |
131 | ||
132 | if (serv) { | |
133 | port = htons(serv->s_port); | |
134 | } else { | |
135 | port = 1344; | |
136 | } | |
137 | } | |
138 | ||
139 | s++; | |
140 | e = strchr(s, '\0'); | |
141 | len = e - s; | |
142 | ||
143 | if (len > 1024) { | |
144 | debug(3, 0) ("icap_service_process (line %d): long resource name (>1024), probably wrong\n", config_lineno); | |
145 | } | |
146 | ||
147 | resource.limitInit(s, len + 1); | |
148 | ||
149 | if ((bypass != 0) && (bypass != 1)) { | |
150 | return false; | |
151 | } | |
152 | ||
153 | return true; | |
154 | ||
155 | }; | |
156 | ||
157 | void ICAPServiceRep::invalidate() | |
158 | { | |
159 | assert(self != NULL); | |
160 | self = NULL; // may destroy us and, hence, invalidate cbdata(this) | |
161 | // TODO: it would be nice to invalidate cbdata(this) when not destroyed | |
162 | } | |
163 | ||
164 | bool ICAPServiceRep::up() const | |
165 | { | |
166 | return self != NULL && theState == stateUp; | |
167 | } | |
168 | ||
169 | bool ICAPServiceRep::wantsPreview(size_t &wantedSize) const | |
170 | { | |
171 | Must(up()); | |
172 | ||
173 | if (theOptions->preview < 0) | |
174 | return false; | |
175 | ||
176 | wantedSize = theOptions->preview; | |
177 | ||
178 | return true; | |
179 | } | |
180 | ||
181 | bool ICAPServiceRep::allows204() const | |
182 | { | |
183 | Must(up()); | |
184 | return true; // in the future, we may have ACLs to prevent 204s | |
185 | } | |
186 | ||
187 | ||
188 | static | |
189 | void ICAPServiceRep_noteTimeToUpdate(void *data) | |
190 | { | |
191 | ICAPServiceRep *service = static_cast<ICAPServiceRep*>(data); | |
192 | Must(service); | |
193 | service->noteTimeToUpdate(); | |
194 | } | |
195 | ||
196 | void ICAPServiceRep::noteTimeToUpdate() | |
197 | { | |
198 | if (!self || waiting()) { | |
199 | debugs(93,5, "ICAPService ignores options update " << status()); | |
200 | return; | |
201 | } | |
202 | ||
203 | debugs(93,5, "ICAPService performs a regular options update " << status()); | |
204 | startGettingOptions(); | |
205 | } | |
206 | ||
207 | static | |
208 | void ICAPServiceRep_noteTimeToNotify(void *data) | |
209 | { | |
210 | ICAPServiceRep *service = static_cast<ICAPServiceRep*>(data); | |
211 | Must(service); | |
212 | service->noteTimeToNotify(); | |
213 | } | |
214 | ||
215 | void ICAPServiceRep::noteTimeToNotify() | |
216 | { | |
217 | Must(!notifying); | |
218 | notifying = true; | |
219 | debugs(93,7, "ICAPService notifies " << theClients.size() << " clients " << | |
220 | status()); | |
221 | ||
222 | // note: we must notify even if we are invalidated | |
223 | ||
224 | Pointer us = NULL; | |
225 | ||
226 | while (!theClients.empty()) { | |
227 | Client i = theClients.pop_back(); | |
228 | us = i.service; // prevent callbacks from destroying us while we loop | |
229 | ||
230 | if (cbdataReferenceValid(i.data)) | |
231 | (*i.callback)(i.data, us); | |
232 | ||
233 | cbdataReferenceDone(i.data); | |
234 | } | |
235 | ||
236 | notifying = false; | |
237 | } | |
238 | ||
239 | void ICAPServiceRep::callWhenReady(Callback *cb, void *data) | |
240 | { | |
241 | Must(cb); | |
242 | Must(self != NULL); | |
243 | ||
244 | Client i; | |
245 | i.service = self; | |
246 | i.callback = cb; | |
247 | i.data = cbdataReference(data); | |
248 | theClients.push_back(i); | |
249 | ||
250 | if (waiting() || notifying) | |
251 | return; // do nothing, we will be picked up in noteTimeToNotify() | |
252 | ||
253 | if (needNewOptions()) | |
254 | startGettingOptions(); | |
255 | else | |
256 | scheduleNotification(); | |
257 | } | |
258 | ||
259 | void ICAPServiceRep::scheduleNotification() | |
260 | { | |
261 | debugs(93,7, "ICAPService will notify " << theClients.size() << " clients"); | |
262 | eventAdd("ICAPServiceRep::noteTimeToNotify", &ICAPServiceRep_noteTimeToNotify, this, 0, 0, true); | |
263 | } | |
264 | ||
265 | bool ICAPServiceRep::waiting() const | |
266 | { | |
267 | return theState == stateWait; | |
268 | } | |
269 | ||
270 | bool ICAPServiceRep::needNewOptions() const | |
271 | { | |
272 | return !theOptions || !theOptions->fresh(); | |
273 | } | |
274 | ||
275 | void ICAPServiceRep::changeOptions(ICAPOptions *newOptions) | |
276 | { | |
277 | debugs(93,9, "ICAPService changes options from " << theOptions << " to " << | |
278 | newOptions); | |
279 | delete theOptions; | |
280 | theOptions = newOptions; | |
281 | } | |
282 | ||
283 | static | |
284 | void ICAPServiceRep_noteNewOptions(ICAPOptXact *x, void *data) | |
285 | { | |
286 | ICAPServiceRep *service = static_cast<ICAPServiceRep*>(data); | |
287 | Must(service); | |
288 | service->noteNewOptions(x); | |
289 | } | |
290 | ||
291 | void ICAPServiceRep::noteNewOptions(ICAPOptXact *x) | |
292 | { | |
293 | Must(x); | |
294 | Must(waiting()); | |
295 | ||
296 | theState = stateDown; // default in case we fail to set new options | |
297 | ||
298 | changeOptions(x->options); | |
299 | x->options = NULL; | |
300 | delete x; | |
301 | ||
302 | if (theOptions && theOptions->valid()) | |
303 | theState = stateUp; | |
304 | ||
305 | debugs(93,6, "ICAPService got new options and is now " << | |
306 | (up() ? "up" : "down")); | |
307 | ||
308 | scheduleUpdate(); | |
309 | ||
310 | scheduleNotification(); | |
311 | } | |
312 | ||
313 | void ICAPServiceRep::startGettingOptions() | |
314 | { | |
315 | debugs(93,6, "ICAPService will get new options " << status()); | |
316 | theState = stateWait; | |
317 | ||
318 | ICAPOptXact *x = new ICAPOptXact; | |
319 | x->start(self, &ICAPServiceRep_noteNewOptions, this); | |
320 | // TODO: timeout incase ICAPOptXact never calls us back? | |
321 | } | |
322 | ||
323 | void ICAPServiceRep::scheduleUpdate() | |
324 | { | |
325 | int delay = -1; | |
326 | ||
327 | if (theOptions && theOptions->valid()) { | |
328 | const time_t expire = theOptions->expire(); | |
329 | ||
330 | if (expire > squid_curtime) | |
331 | delay = expire - squid_curtime; | |
332 | else | |
333 | if (expire >= 0) | |
334 | delay = 1; // delay for expired or 'expiring now' options | |
335 | else | |
336 | delay = 60*60; // default for options w/o known expiration time | |
337 | } else { | |
338 | delay = 5*60; // delay for a down service | |
339 | } | |
340 | ||
341 | if (delay <= 0) { | |
342 | debugs(93,0, "internal error: ICAPServiceRep failed to compute options update schedule"); | |
343 | delay = 5*60; // delay for an internal error | |
344 | } | |
345 | ||
346 | // with zero delay, the state changes to stateWait before | |
347 | // notifications are sent out to clients | |
348 | assert(delay > 0); | |
349 | ||
350 | debugs(93,7, "ICAPService will update options in " << delay << " sec"); | |
351 | ||
352 | eventAdd("ICAPServiceRep::noteTimeToUpdate", | |
353 | &ICAPServiceRep_noteTimeToUpdate, this, delay, 0, true); | |
354 | ||
355 | // XXX: prompt updates of valid options should not disable concurrent ICAP | |
356 | // xactions. 'Wait' state should not mark the service 'down'! This will | |
357 | // also remove 'delay == 0' as a special case above. | |
358 | } | |
359 | ||
360 | const char *ICAPServiceRep::status() const | |
361 | { | |
362 | if (!self) | |
363 | return "[invalidated]"; | |
364 | ||
365 | switch (theState) { | |
366 | ||
367 | case stateInit: | |
368 | return "[init]"; | |
369 | ||
370 | case stateWait: | |
371 | return "[wait]"; | |
372 | ||
373 | case stateUp: | |
374 | return "[up]"; | |
375 | ||
376 | case stateDown: | |
377 | return "[down]"; | |
378 | } | |
379 | ||
380 | return "[unknown]"; | |
381 | } |