]>
Commit | Line | Data |
---|---|---|
cd304fc2 | 1 | /* |
cd304fc2 | 2 | * DEBUG: |
3 | * AUTHOR: Duane Wessels | |
4 | * | |
5 | * SQUID Web Proxy Cache http://www.squid-cache.org/ | |
6 | * ---------------------------------------------------------- | |
7 | * | |
8 | * Squid is the result of efforts by numerous individuals from | |
9 | * the Internet community; see the CONTRIBUTORS file for full | |
10 | * details. Many organizations have provided support for Squid's | |
11 | * development; see the SPONSORS file for full details. Squid is | |
12 | * Copyrighted (C) 2001 by the Regents of the University of | |
13 | * California; see the COPYRIGHT file for full details. Squid | |
14 | * incorporates software developed and/or copyrighted by other | |
15 | * sources; see the CREDITS file for full details. | |
16 | * | |
17 | * This program is free software; you can redistribute it and/or modify | |
18 | * it under the terms of the GNU General Public License as published by | |
19 | * the Free Software Foundation; either version 2 of the License, or | |
20 | * (at your option) any later version. | |
9e008dda | 21 | * |
cd304fc2 | 22 | * This program is distributed in the hope that it will be useful, |
23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
25 | * GNU General Public License for more details. | |
9e008dda | 26 | * |
cd304fc2 | 27 | * You should have received a copy of the GNU General Public License |
28 | * along with this program; if not, write to the Free Software | |
29 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. | |
30 | * | |
31 | */ | |
32 | ||
582c2af2 | 33 | #include "squid.h" |
3af10ac0 | 34 | #include "acl/Gadgets.h" |
3d93a84d | 35 | #include "base/TextException.h" |
6b679a01 AJ |
36 | #include "comm/Connection.h" |
37 | #include "comm/forward.h" | |
ec41b64c | 38 | #include "comm/Write.h" |
64b66b76 | 39 | #include "err_detail_type.h" |
582c2af2 | 40 | #include "errorpage.h" |
602d9612 | 41 | #include "fd.h" |
582c2af2 FC |
42 | #include "HttpReply.h" |
43 | #include "HttpRequest.h" | |
582c2af2 | 44 | #include "Server.h" |
3ff65596 | 45 | #include "SquidTime.h" |
582c2af2 FC |
46 | #include "StatCounters.h" |
47 | #include "Store.h" | |
4e540555 | 48 | #include "tools.h" |
b1bd952a | 49 | #include "URL.h" |
5f8252d2 | 50 | |
a83c6ed6 | 51 | #if USE_ADAPTATION |
62c7f90e | 52 | #include "adaptation/AccessCheck.h" |
1adcebc3 | 53 | #include "adaptation/Answer.h" |
a22e6cd3 | 54 | #include "adaptation/Iterator.h" |
0ad2b63b | 55 | #include "base/AsyncCall.h" |
4d5904f7 | 56 | #include "SquidConfig.h" |
0f283edf | 57 | #endif |
cd304fc2 | 58 | |
c1520b67 | 59 | // implemented in client_side_reply.cc until sides have a common parent |
82afb125 | 60 | void purgeEntriesByUrl(HttpRequest * req, const char *url); |
c1520b67 | 61 | |
39cb8c41 AR |
62 | ServerStateData::ServerStateData(FwdState *theFwdState): AsyncJob("ServerStateData"), |
63 | requestSender(NULL), | |
a83c6ed6 | 64 | #if USE_ADAPTATION |
39cb8c41 AR |
65 | adaptedHeadSource(NULL), |
66 | adaptationAccessCheckPending(false), | |
67 | startedAdaptation(false), | |
77089558 | 68 | #endif |
0ad2b63b CT |
69 | receivedWholeRequestBody(false), |
70 | theVirginReply(NULL), | |
71 | theFinalReply(NULL) | |
cd304fc2 | 72 | { |
73 | fwd = theFwdState; | |
74 | entry = fwd->entry; | |
34266cde | 75 | |
5f8252d2 | 76 | entry->lock(); |
34266cde | 77 | |
b248c2a3 AJ |
78 | request = fwd->request; |
79 | HTTPMSGLOCK(request); | |
cd304fc2 | 80 | } |
81 | ||
82 | ServerStateData::~ServerStateData() | |
83 | { | |
49918309 AR |
84 | // paranoid: check that swanSong has been called |
85 | assert(!requestBodySource); | |
86 | #if USE_ADAPTATION | |
87 | assert(!virginBodyDestination); | |
88 | assert(!adaptedBodySource); | |
89 | #endif | |
90 | ||
97b5e68f | 91 | entry->unlock(); |
cd304fc2 | 92 | |
6dd9f4bd | 93 | HTTPMSGUNLOCK(request); |
585ab260 | 94 | HTTPMSGUNLOCK(theVirginReply); |
95 | HTTPMSGUNLOCK(theFinalReply); | |
cd304fc2 | 96 | |
97 | fwd = NULL; // refcounted | |
98 | ||
49918309 | 99 | if (responseBodyBuffer != NULL) { |
9e008dda AJ |
100 | delete responseBodyBuffer; |
101 | responseBodyBuffer = NULL; | |
49918309 AR |
102 | } |
103 | } | |
104 | ||
105 | void | |
106 | ServerStateData::swanSong() | |
107 | { | |
108 | // get rid of our piping obligations | |
5f8252d2 | 109 | if (requestBodySource != NULL) |
279152e7 | 110 | stopConsumingFrom(requestBodySource); |
5f8252d2 | 111 | |
a83c6ed6 AR |
112 | #if USE_ADAPTATION |
113 | cleanAdaptation(); | |
5f8252d2 | 114 | #endif |
7dc79973 | 115 | |
49918309 AR |
116 | BodyConsumer::swanSong(); |
117 | #if USE_ADAPTATION | |
118 | Initiator::swanSong(); | |
119 | BodyProducer::swanSong(); | |
120 | #endif | |
b2c251cc AJ |
121 | |
122 | // paranoid: check that swanSong has been called | |
123 | // extra paranoid: yeah, I really mean it. they MUST pass here. | |
124 | assert(!requestBodySource); | |
125 | #if USE_ADAPTATION | |
126 | assert(!virginBodyDestination); | |
127 | assert(!adaptedBodySource); | |
128 | #endif | |
5f8252d2 | 129 | } |
130 | ||
585ab260 | 131 | HttpReply * |
9e008dda AJ |
132 | ServerStateData::virginReply() |
133 | { | |
585ab260 | 134 | assert(theVirginReply); |
135 | return theVirginReply; | |
136 | } | |
137 | ||
138 | const HttpReply * | |
9e008dda AJ |
139 | ServerStateData::virginReply() const |
140 | { | |
585ab260 | 141 | assert(theVirginReply); |
142 | return theVirginReply; | |
143 | } | |
144 | ||
145 | HttpReply * | |
9e008dda AJ |
146 | ServerStateData::setVirginReply(HttpReply *rep) |
147 | { | |
585ab260 | 148 | debugs(11,5, HERE << this << " setting virgin reply to " << rep); |
149 | assert(!theVirginReply); | |
150 | assert(rep); | |
b248c2a3 AJ |
151 | theVirginReply = rep; |
152 | HTTPMSGLOCK(theVirginReply); | |
9e008dda | 153 | return theVirginReply; |
585ab260 | 154 | } |
155 | ||
156 | HttpReply * | |
9e008dda AJ |
157 | ServerStateData::finalReply() |
158 | { | |
585ab260 | 159 | assert(theFinalReply); |
160 | return theFinalReply; | |
161 | } | |
162 | ||
163 | HttpReply * | |
9e008dda AJ |
164 | ServerStateData::setFinalReply(HttpReply *rep) |
165 | { | |
585ab260 | 166 | debugs(11,5, HERE << this << " setting final reply to " << rep); |
167 | ||
168 | assert(!theFinalReply); | |
169 | assert(rep); | |
b248c2a3 AJ |
170 | theFinalReply = rep; |
171 | HTTPMSGLOCK(theFinalReply); | |
585ab260 | 172 | |
3756e5c0 AR |
173 | // give entry the reply because haveParsedReplyHeaders() expects it there |
174 | entry->replaceHttpReply(theFinalReply, false); // but do not write yet | |
175 | haveParsedReplyHeaders(); // update the entry/reply (e.g., set timestamps) | |
176 | entry->startWriting(); // write the updated entry to store | |
585ab260 | 177 | |
178 | return theFinalReply; | |
179 | } | |
180 | ||
5f8252d2 | 181 | // called when no more server communication is expected; may quit |
182 | void | |
183 | ServerStateData::serverComplete() | |
184 | { | |
185 | debugs(11,5,HERE << "serverComplete " << this); | |
186 | ||
187 | if (!doneWithServer()) { | |
188 | closeServer(); | |
189 | assert(doneWithServer()); | |
190 | } | |
191 | ||
7dc79973 | 192 | completed = true; |
193 | ||
3ff65596 AR |
194 | HttpRequest *r = originalRequest(); |
195 | r->hier.total_response_time = r->hier.first_conn_start.tv_sec ? | |
e1381638 AJ |
196 | tvSubMsec(r->hier.first_conn_start, current_time) : -1; |
197 | ||
5f8252d2 | 198 | if (requestBodySource != NULL) |
199 | stopConsumingFrom(requestBodySource); | |
200 | ||
7dc79973 | 201 | if (responseBodyBuffer != NULL) |
9e008dda | 202 | return; |
7dc79973 | 203 | |
204 | serverComplete2(); | |
205 | } | |
206 | ||
207 | void | |
208 | ServerStateData::serverComplete2() | |
209 | { | |
210 | debugs(11,5,HERE << "serverComplete2 " << this); | |
211 | ||
a83c6ed6 | 212 | #if USE_ADAPTATION |
5f8252d2 | 213 | if (virginBodyDestination != NULL) |
214 | stopProducingFor(virginBodyDestination, true); | |
215 | ||
a83c6ed6 | 216 | if (!doneWithAdaptation()) |
5f8252d2 | 217 | return; |
218 | #endif | |
219 | ||
220 | completeForwarding(); | |
5f8252d2 | 221 | } |
222 | ||
79628299 | 223 | bool ServerStateData::doneAll() const |
9e008dda | 224 | { |
79628299 | 225 | return doneWithServer() && |
a83c6ed6 | 226 | #if USE_ADAPTATION |
b692311b A |
227 | doneWithAdaptation() && |
228 | Adaptation::Initiator::doneAll() && | |
229 | BodyProducer::doneAll() && | |
5f8252d2 | 230 | #endif |
b692311b | 231 | BodyConsumer::doneAll(); |
5f8252d2 | 232 | } |
233 | ||
234 | // FTP side overloads this to work around multiple calls to fwd->complete | |
235 | void | |
9e008dda AJ |
236 | ServerStateData::completeForwarding() |
237 | { | |
5f8252d2 | 238 | debugs(11,5, HERE << "completing forwarding for " << fwd); |
239 | assert(fwd != NULL); | |
240 | fwd->complete(); | |
241 | } | |
242 | ||
123ec4de | 243 | // Register to receive request body |
244 | bool ServerStateData::startRequestBodyFlow() | |
245 | { | |
246 | HttpRequest *r = originalRequest(); | |
247 | assert(r->body_pipe != NULL); | |
248 | requestBodySource = r->body_pipe; | |
249 | if (requestBodySource->setConsumerIfNotLate(this)) { | |
250 | debugs(11,3, HERE << "expecting request body from " << | |
9e008dda | 251 | requestBodySource->status()); |
123ec4de | 252 | return true; |
253 | } | |
254 | ||
255 | debugs(11,3, HERE << "aborting on partially consumed request body: " << | |
9e008dda | 256 | requestBodySource->status()); |
123ec4de | 257 | requestBodySource = NULL; |
258 | return false; | |
259 | } | |
260 | ||
5f8252d2 | 261 | // Entry-dependent callbacks use this check to quit if the entry went bad |
262 | bool | |
263 | ServerStateData::abortOnBadEntry(const char *abortReason) | |
264 | { | |
265 | if (entry->isAccepting()) | |
266 | return false; | |
267 | ||
268 | debugs(11,5, HERE << "entry is not Accepting!"); | |
269 | abortTransaction(abortReason); | |
270 | return true; | |
271 | } | |
272 | ||
273 | // more request or adapted response body is available | |
274 | void | |
dc56a9b1 | 275 | ServerStateData::noteMoreBodyDataAvailable(BodyPipe::Pointer bp) |
5f8252d2 | 276 | { |
a83c6ed6 | 277 | #if USE_ADAPTATION |
dc56a9b1 | 278 | if (adaptedBodySource == bp) { |
5f8252d2 | 279 | handleMoreAdaptedBodyAvailable(); |
280 | return; | |
281 | } | |
282 | #endif | |
0ad2b63b CT |
283 | if (requestBodySource == bp) |
284 | handleMoreRequestBodyAvailable(); | |
5f8252d2 | 285 | } |
286 | ||
287 | // the entire request or adapted response body was provided, successfully | |
288 | void | |
dc56a9b1 | 289 | ServerStateData::noteBodyProductionEnded(BodyPipe::Pointer bp) |
5f8252d2 | 290 | { |
a83c6ed6 | 291 | #if USE_ADAPTATION |
dc56a9b1 | 292 | if (adaptedBodySource == bp) { |
5f8252d2 | 293 | handleAdaptedBodyProductionEnded(); |
294 | return; | |
c99de607 | 295 | } |
cd304fc2 | 296 | #endif |
0ad2b63b CT |
297 | if (requestBodySource == bp) |
298 | handleRequestBodyProductionEnded(); | |
5f8252d2 | 299 | } |
300 | ||
301 | // premature end of the request or adapted response body production | |
302 | void | |
dc56a9b1 | 303 | ServerStateData::noteBodyProducerAborted(BodyPipe::Pointer bp) |
5f8252d2 | 304 | { |
a83c6ed6 | 305 | #if USE_ADAPTATION |
dc56a9b1 | 306 | if (adaptedBodySource == bp) { |
5f8252d2 | 307 | handleAdaptedBodyProducerAborted(); |
308 | return; | |
309 | } | |
310 | #endif | |
0ad2b63b CT |
311 | if (requestBodySource == bp) |
312 | handleRequestBodyProducerAborted(); | |
5f8252d2 | 313 | } |
314 | ||
5f8252d2 | 315 | // more origin request body data is available |
316 | void | |
317 | ServerStateData::handleMoreRequestBodyAvailable() | |
318 | { | |
319 | if (!requestSender) | |
320 | sendMoreRequestBody(); | |
321 | else | |
322 | debugs(9,3, HERE << "waiting for request body write to complete"); | |
323 | } | |
324 | ||
325 | // there will be no more handleMoreRequestBodyAvailable calls | |
326 | void | |
327 | ServerStateData::handleRequestBodyProductionEnded() | |
328 | { | |
39cb8c41 | 329 | receivedWholeRequestBody = true; |
5f8252d2 | 330 | if (!requestSender) |
331 | doneSendingRequestBody(); | |
332 | else | |
333 | debugs(9,3, HERE << "waiting for request body write to complete"); | |
334 | } | |
335 | ||
336 | // called when we are done sending request body; kids extend this | |
337 | void | |
9e008dda AJ |
338 | ServerStateData::doneSendingRequestBody() |
339 | { | |
5f8252d2 | 340 | debugs(9,3, HERE << "done sending request body"); |
341 | assert(requestBodySource != NULL); | |
342 | stopConsumingFrom(requestBodySource); | |
343 | ||
344 | // kids extend this | |
345 | } | |
346 | ||
347 | // called when body producers aborts; kids extend this | |
348 | void | |
349 | ServerStateData::handleRequestBodyProducerAborted() | |
350 | { | |
351 | if (requestSender != NULL) | |
352 | debugs(9,3, HERE << "fyi: request body aborted while we were sending"); | |
353 | ||
0919c51e | 354 | fwd->dontRetry(true); // the problem is not with the server |
5f8252d2 | 355 | stopConsumingFrom(requestBodySource); // requestSender, if any, will notice |
356 | ||
357 | // kids extend this | |
358 | } | |
359 | ||
5f8252d2 | 360 | // called when we wrote request headers(!) or a part of the body |
361 | void | |
dc56a9b1 | 362 | ServerStateData::sentRequestBody(const CommIoCbParams &io) |
5f8252d2 | 363 | { |
dc56a9b1 | 364 | debugs(11, 5, "sentRequestBody: FD " << io.fd << ": size " << io.size << ": errflag " << io.flag << "."); |
5f8252d2 | 365 | debugs(32,3,HERE << "sentRequestBody called"); |
366 | ||
367 | requestSender = NULL; | |
368 | ||
dc56a9b1 | 369 | if (io.size > 0) { |
370 | fd_bytes(io.fd, io.size, FD_WRITE); | |
e4f1fdae | 371 | kb_incr(&(statCounter.server.all.kbytes_out), io.size); |
5f8252d2 | 372 | // kids should increment their counters |
373 | } | |
374 | ||
dc56a9b1 | 375 | if (io.flag == COMM_ERR_CLOSING) |
5f8252d2 | 376 | return; |
377 | ||
378 | if (!requestBodySource) { | |
379 | debugs(9,3, HERE << "detected while-we-were-sending abort"); | |
380 | return; // do nothing; | |
381 | } | |
382 | ||
dc56a9b1 | 383 | if (io.flag) { |
e0236918 | 384 | debugs(11, DBG_IMPORTANT, "sentRequestBody error: FD " << io.fd << ": " << xstrerr(io.xerrno)); |
5f8252d2 | 385 | ErrorState *err; |
955394ce | 386 | err = new ErrorState(ERR_WRITE_ERROR, Http::scBadGateway, fwd->request); |
f5f9e44c | 387 | err->xerrno = io.xerrno; |
5f8252d2 | 388 | fwd->fail(err); |
389 | abortTransaction("I/O error while sending request body"); | |
390 | return; | |
391 | } | |
392 | ||
393 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { | |
394 | abortTransaction("store entry aborted while sending request body"); | |
395 | return; | |
396 | } | |
397 | ||
39cb8c41 AR |
398 | if (!requestBodySource->exhausted()) |
399 | sendMoreRequestBody(); | |
de48b288 | 400 | else if (receivedWholeRequestBody) |
5f8252d2 | 401 | doneSendingRequestBody(); |
402 | else | |
39cb8c41 | 403 | debugs(9,3, HERE << "waiting for body production end or abort"); |
cd304fc2 | 404 | } |
405 | ||
5f8252d2 | 406 | void |
407 | ServerStateData::sendMoreRequestBody() | |
408 | { | |
409 | assert(requestBodySource != NULL); | |
410 | assert(!requestSender); | |
a0297974 | 411 | |
e83cc785 | 412 | const Comm::ConnectionPointer conn = dataConnection(); |
a0297974 | 413 | |
6b679a01 | 414 | if (!Comm::IsConnOpen(conn)) { |
e0d28505 | 415 | debugs(9,3, HERE << "cannot send request body to closing " << conn); |
a0297974 AR |
416 | return; // wait for the kid's close handler; TODO: assert(closer); |
417 | } | |
418 | ||
5f8252d2 | 419 | MemBuf buf; |
39cb8c41 | 420 | if (getMoreRequestBody(buf) && buf.contentSize() > 0) { |
5f8252d2 | 421 | debugs(9,3, HERE << "will write " << buf.contentSize() << " request body bytes"); |
9e008dda | 422 | typedef CommCbMemFunT<ServerStateData, CommIoCbParams> Dialer; |
e0d28505 | 423 | requestSender = JobCallback(93,3, Dialer, this, ServerStateData::sentRequestBody); |
b0388924 | 424 | Comm::Write(conn, &buf, requestSender); |
5f8252d2 | 425 | } else { |
426 | debugs(9,3, HERE << "will wait for more request body bytes or eof"); | |
427 | requestSender = NULL; | |
428 | } | |
429 | } | |
430 | ||
39cb8c41 AR |
431 | /// either fill buf with available [encoded] request body bytes or return false |
432 | bool | |
433 | ServerStateData::getMoreRequestBody(MemBuf &buf) | |
434 | { | |
435 | // default implementation does not encode request body content | |
436 | Must(requestBodySource != NULL); | |
437 | return requestBodySource->getMoreData(buf); | |
438 | } | |
439 | ||
c1520b67 AJ |
440 | // Compares hosts in urls, returns false if different, no sheme, or no host. |
441 | static bool | |
442 | sameUrlHosts(const char *url1, const char *url2) | |
443 | { | |
444 | // XXX: Want urlHostname() here, but it uses static storage and copying | |
445 | const char *host1 = strchr(url1, ':'); | |
446 | const char *host2 = strchr(url2, ':'); | |
447 | ||
448 | if (host1 && host2) { | |
449 | // skip scheme slashes | |
450 | do { | |
451 | ++host1; | |
452 | ++host2; | |
453 | } while (*host1 == '/' && *host2 == '/'); | |
454 | ||
455 | if (!*host1) | |
456 | return false; // no host | |
457 | ||
458 | // increment while the same until we reach the end of the URL/host | |
459 | while (*host1 && *host1 != '/' && *host1 == *host2) { | |
460 | ++host1; | |
461 | ++host2; | |
462 | } | |
463 | return *host1 == *host2; | |
464 | } | |
465 | ||
466 | return false; // no URL scheme | |
467 | } | |
468 | ||
469 | // purges entries that match the value of a given HTTP [response] header | |
470 | static void | |
90bd689c | 471 | purgeEntriesByHeader(HttpRequest *req, const char *reqUrl, HttpMsg *rep, http_hdr_type hdr) |
c1520b67 | 472 | { |
bf956b0a | 473 | const char *hdrUrl, *absUrl; |
9e008dda | 474 | |
71051277 BR |
475 | absUrl = NULL; |
476 | hdrUrl = rep->header.getStr(hdr); | |
477 | if (hdrUrl == NULL) { | |
478 | return; | |
479 | } | |
9e008dda | 480 | |
71051277 BR |
481 | /* |
482 | * If the URL is relative, make it absolute so we can find it. | |
483 | * If it's absolute, make sure the host parts match to avoid DOS attacks | |
484 | * as per RFC 2616 13.10. | |
485 | */ | |
486 | if (urlIsRelative(hdrUrl)) { | |
487 | absUrl = urlMakeAbsolute(req, hdrUrl); | |
3cbbd242 | 488 | if (absUrl != NULL) { |
71051277 | 489 | hdrUrl = absUrl; |
3cbbd242 | 490 | } |
71051277 BR |
491 | } else if (!sameUrlHosts(reqUrl, hdrUrl)) { |
492 | return; | |
493 | } | |
9e008dda | 494 | |
8dceeee3 | 495 | purgeEntriesByUrl(req, hdrUrl); |
9e008dda | 496 | |
71051277 BR |
497 | if (absUrl != NULL) { |
498 | safe_free(absUrl); | |
3cbbd242 | 499 | } |
c1520b67 AJ |
500 | } |
501 | ||
502 | // some HTTP methods should purge matching cache entries | |
503 | void | |
504 | ServerStateData::maybePurgeOthers() | |
505 | { | |
9e008dda AJ |
506 | // only some HTTP methods should purge matching cache entries |
507 | if (!request->method.purgesOthers()) | |
508 | return; | |
c1520b67 | 509 | |
9e008dda | 510 | // and probably only if the response was successful |
9b769c67 | 511 | if (theFinalReply->sline.status() >= 400) |
9e008dda | 512 | return; |
c1520b67 | 513 | |
9e008dda AJ |
514 | // XXX: should we use originalRequest() here? |
515 | const char *reqUrl = urlCanonical(request); | |
516 | debugs(88, 5, "maybe purging due to " << RequestMethodStr(request->method) << ' ' << reqUrl); | |
517 | purgeEntriesByUrl(request, reqUrl); | |
518 | purgeEntriesByHeader(request, reqUrl, theFinalReply, HDR_LOCATION); | |
519 | purgeEntriesByHeader(request, reqUrl, theFinalReply, HDR_CONTENT_LOCATION); | |
c1520b67 AJ |
520 | } |
521 | ||
fad1a21e | 522 | /// called when we have final (possibly adapted) reply headers; kids extend |
5f8252d2 | 523 | void |
524 | ServerStateData::haveParsedReplyHeaders() | |
525 | { | |
9e008dda AJ |
526 | Must(theFinalReply); |
527 | maybePurgeOthers(); | |
5f8252d2 | 528 | } |
529 | ||
7dc79973 | 530 | HttpRequest * |
531 | ServerStateData::originalRequest() | |
532 | { | |
533 | return request; | |
534 | } | |
5f8252d2 | 535 | |
a83c6ed6 | 536 | #if USE_ADAPTATION |
a22e6cd3 AR |
537 | /// Initiate an asynchronous adaptation transaction which will call us back. |
538 | void | |
539 | ServerStateData::startAdaptation(const Adaptation::ServiceGroupPointer &group, HttpRequest *cause) | |
cd304fc2 | 540 | { |
a83c6ed6 | 541 | debugs(11, 5, "ServerStateData::startAdaptation() called"); |
5f8252d2 | 542 | // check whether we should be sending a body as well |
5f8252d2 | 543 | // start body pipe to feed ICAP transaction if needed |
585ab260 | 544 | assert(!virginBodyDestination); |
9e008dda | 545 | HttpReply *vrep = virginReply(); |
585ab260 | 546 | assert(!vrep->body_pipe); |
47f6e231 | 547 | int64_t size = 0; |
585ab260 | 548 | if (vrep->expectingBody(cause->method, size) && size) { |
5f8252d2 | 549 | virginBodyDestination = new BodyPipe(this); |
585ab260 | 550 | vrep->body_pipe = virginBodyDestination; |
9e008dda AJ |
551 | debugs(93, 6, HERE << "will send virgin reply body to " << |
552 | virginBodyDestination << "; size: " << size); | |
c2eef5bd | 553 | if (size > 0) |
554 | virginBodyDestination->setBodySize(size); | |
5f8252d2 | 555 | } |
556 | ||
a22e6cd3 | 557 | adaptedHeadSource = initiateAdaptation( |
4cb2536f | 558 | new Adaptation::Iterator(vrep, cause, group)); |
4299f876 | 559 | startedAdaptation = initiated(adaptedHeadSource); |
a22e6cd3 | 560 | Must(startedAdaptation); |
cd304fc2 | 561 | } |
0c25e715 | 562 | |
5f8252d2 | 563 | // properly cleans up ICAP-related state |
564 | // may be called multiple times | |
9e008dda AJ |
565 | void ServerStateData::cleanAdaptation() |
566 | { | |
a83c6ed6 | 567 | debugs(11,5, HERE << "cleaning ICAP; ACL: " << adaptationAccessCheckPending); |
5f8252d2 | 568 | |
569 | if (virginBodyDestination != NULL) | |
570 | stopProducingFor(virginBodyDestination, false); | |
571 | ||
0f283edf | 572 | announceInitiatorAbort(adaptedHeadSource); |
5f8252d2 | 573 | |
574 | if (adaptedBodySource != NULL) | |
575 | stopConsumingFrom(adaptedBodySource); | |
576 | ||
a83c6ed6 AR |
577 | if (!adaptationAccessCheckPending) // we cannot cancel a pending callback |
578 | assert(doneWithAdaptation()); // make sure the two methods are in sync | |
5f8252d2 | 579 | } |
580 | ||
581 | bool | |
9e008dda AJ |
582 | ServerStateData::doneWithAdaptation() const |
583 | { | |
a83c6ed6 | 584 | return !adaptationAccessCheckPending && |
9e008dda | 585 | !virginBodyDestination && !adaptedHeadSource && !adaptedBodySource; |
5f8252d2 | 586 | } |
587 | ||
bc81cb2b | 588 | // sends virgin reply body to ICAP, buffering excesses if needed |
589 | void | |
590 | ServerStateData::adaptVirginReplyBody(const char *data, ssize_t len) | |
591 | { | |
a83c6ed6 | 592 | assert(startedAdaptation); |
bc81cb2b | 593 | |
594 | if (!virginBodyDestination) { | |
595 | debugs(11,3, HERE << "ICAP does not want more virgin body"); | |
596 | return; | |
597 | } | |
598 | ||
599 | // grow overflow area if already overflowed | |
600 | if (responseBodyBuffer) { | |
601 | responseBodyBuffer->append(data, len); | |
602 | data = responseBodyBuffer->content(); | |
603 | len = responseBodyBuffer->contentSize(); | |
604 | } | |
605 | ||
606 | const ssize_t putSize = virginBodyDestination->putMoreData(data, len); | |
607 | data += putSize; | |
608 | len -= putSize; | |
609 | ||
610 | // if we had overflow area, shrink it as necessary | |
611 | if (responseBodyBuffer) { | |
612 | if (putSize == responseBodyBuffer->contentSize()) { | |
613 | delete responseBodyBuffer; | |
614 | responseBodyBuffer = NULL; | |
615 | } else { | |
616 | responseBodyBuffer->consume(putSize); | |
9e008dda | 617 | } |
bc81cb2b | 618 | return; |
619 | } | |
620 | ||
621 | // if we did not have an overflow area, create it as needed | |
622 | if (len > 0) { | |
623 | assert(!responseBodyBuffer); | |
624 | responseBodyBuffer = new MemBuf; | |
625 | responseBodyBuffer->init(4096, SQUID_TCP_SO_RCVBUF * 10); | |
626 | responseBodyBuffer->append(data, len); | |
627 | } | |
628 | } | |
629 | ||
5f8252d2 | 630 | // can supply more virgin response body data |
631 | void | |
dc56a9b1 | 632 | ServerStateData::noteMoreBodySpaceAvailable(BodyPipe::Pointer) |
5f8252d2 | 633 | { |
7dc79973 | 634 | if (responseBodyBuffer) { |
bc81cb2b | 635 | addVirginReplyBody(NULL, 0); // kick the buffered fragment alive again |
636 | if (completed && !responseBodyBuffer) { | |
637 | serverComplete2(); | |
638 | return; | |
639 | } | |
7dc79973 | 640 | } |
5f8252d2 | 641 | maybeReadVirginBody(); |
642 | } | |
643 | ||
bc81cb2b | 644 | // the consumer of our virgin response body aborted |
5f8252d2 | 645 | void |
dc56a9b1 | 646 | ServerStateData::noteBodyConsumerAborted(BodyPipe::Pointer) |
5f8252d2 | 647 | { |
648 | stopProducingFor(virginBodyDestination, false); | |
bc81cb2b | 649 | |
a83c6ed6 | 650 | // do not force closeServer here in case we need to bypass AdaptationQueryAbort |
bc81cb2b | 651 | |
a83c6ed6 AR |
652 | if (doneWithAdaptation()) // we may still be receiving adapted response |
653 | handleAdaptationCompleted(); | |
5f8252d2 | 654 | } |
655 | ||
656 | // received adapted response headers (body may follow) | |
657 | void | |
3af10ac0 | 658 | ServerStateData::noteAdaptationAnswer(const Adaptation::Answer &answer) |
5f8252d2 | 659 | { |
a83c6ed6 | 660 | clearAdaptation(adaptedHeadSource); // we do not expect more messages |
5f8252d2 | 661 | |
3af10ac0 AR |
662 | switch (answer.kind) { |
663 | case Adaptation::Answer::akForward: | |
b248c2a3 | 664 | handleAdaptedHeader(const_cast<HttpMsg*>(answer.message.getRaw())); |
3af10ac0 AR |
665 | break; |
666 | ||
667 | case Adaptation::Answer::akBlock: | |
668 | handleAdaptationBlocked(answer); | |
669 | break; | |
670 | ||
671 | case Adaptation::Answer::akError: | |
672 | handleAdaptationAborted(!answer.final); | |
673 | break; | |
674 | } | |
675 | } | |
676 | ||
677 | void | |
678 | ServerStateData::handleAdaptedHeader(HttpMsg *msg) | |
679 | { | |
1733bbba CT |
680 | if (abortOnBadEntry("entry went bad while waiting for adapted headers")) { |
681 | // If the adapted response has a body, the ICAP side needs to know | |
682 | // that nobody will consume that body. We will be destroyed upon | |
683 | // return. Tell the ICAP side that it is on its own. | |
684 | HttpReply *rep = dynamic_cast<HttpReply*>(msg); | |
685 | assert(rep); | |
686 | if (rep->body_pipe != NULL) | |
687 | rep->body_pipe->expectNoConsumption(); | |
688 | ||
5f8252d2 | 689 | return; |
1733bbba | 690 | } |
5f8252d2 | 691 | |
585ab260 | 692 | HttpReply *rep = dynamic_cast<HttpReply*>(msg); |
5f8252d2 | 693 | assert(rep); |
585ab260 | 694 | debugs(11,5, HERE << this << " setting adapted reply to " << rep); |
695 | setFinalReply(rep); | |
5f8252d2 | 696 | |
697 | assert(!adaptedBodySource); | |
585ab260 | 698 | if (rep->body_pipe != NULL) { |
5f8252d2 | 699 | // subscribe to receive adapted body |
585ab260 | 700 | adaptedBodySource = rep->body_pipe; |
5f8252d2 | 701 | // assume that ICAP does not auto-consume on failures |
dfb6dc9d NH |
702 | const bool result = adaptedBodySource->setConsumerIfNotLate(this); |
703 | assert(result); | |
5f8252d2 | 704 | } else { |
705 | // no body | |
a83c6ed6 AR |
706 | if (doneWithAdaptation()) // we may still be sending virgin response |
707 | handleAdaptationCompleted(); | |
5f8252d2 | 708 | } |
5f8252d2 | 709 | } |
710 | ||
5f8252d2 | 711 | void |
0ad2b63b | 712 | ServerStateData::resumeBodyStorage() |
5f8252d2 | 713 | { |
0ad2b63b CT |
714 | if (abortOnBadEntry("store entry aborted while kick producer callback")) |
715 | return; | |
5f8252d2 | 716 | |
e83cdc25 | 717 | if (!adaptedBodySource) |
0ad2b63b CT |
718 | return; |
719 | ||
720 | handleMoreAdaptedBodyAvailable(); | |
721 | ||
722 | if (adaptedBodySource != NULL && adaptedBodySource->exhausted()) | |
723 | endAdaptedBodyConsumption(); | |
724 | } | |
5f8252d2 | 725 | |
0ad2b63b CT |
726 | // more adapted response body is available |
727 | void | |
728 | ServerStateData::handleMoreAdaptedBodyAvailable() | |
729 | { | |
5f8252d2 | 730 | if (abortOnBadEntry("entry refuses adapted body")) |
731 | return; | |
732 | ||
733 | assert(entry); | |
0ad2b63b CT |
734 | |
735 | size_t contentSize = adaptedBodySource->buf().contentSize(); | |
0ad2b63b CT |
736 | |
737 | if (!contentSize) | |
738 | return; // XXX: bytesWanted asserts on zero-size ranges | |
739 | ||
384a7590 | 740 | const size_t spaceAvailable = entry->bytesWanted(Range<size_t>(0, contentSize), true); |
0ad2b63b | 741 | |
e83cdc25 | 742 | if (spaceAvailable < contentSize ) { |
0ad2b63b CT |
743 | // No or partial body data consuming |
744 | typedef NullaryMemFunT<ServerStateData> Dialer; | |
745 | AsyncCall::Pointer call = asyncCall(93, 5, "ServerStateData::resumeBodyStorage", | |
746 | Dialer(this, &ServerStateData::resumeBodyStorage)); | |
747 | entry->deferProducer(call); | |
748 | } | |
749 | ||
4dc2b072 | 750 | if (!spaceAvailable) { |
0ad2b63b CT |
751 | debugs(11, 5, HERE << "NOT storing " << contentSize << " bytes of adapted " << |
752 | "response body at offset " << adaptedBodySource->consumedSize()); | |
753 | return; | |
754 | } | |
e83cdc25 | 755 | |
0ad2b63b CT |
756 | if (spaceAvailable < contentSize ) { |
757 | debugs(11, 5, HERE << "postponing storage of " << | |
758 | (contentSize - spaceAvailable) << " body bytes"); | |
759 | contentSize = spaceAvailable; | |
0ad2b63b | 760 | } |
e83cdc25 | 761 | |
0ad2b63b CT |
762 | debugs(11,5, HERE << "storing " << contentSize << " bytes of adapted " << |
763 | "response body at offset " << adaptedBodySource->consumedSize()); | |
e83cdc25 | 764 | |
5f8252d2 | 765 | BodyPipeCheckout bpc(*adaptedBodySource); |
0ad2b63b CT |
766 | const StoreIOBuffer ioBuf(&bpc.buf, currentOffset, contentSize); |
767 | currentOffset += ioBuf.length; | |
5f8252d2 | 768 | entry->write(ioBuf); |
769 | bpc.buf.consume(contentSize); | |
770 | bpc.checkIn(); | |
771 | } | |
772 | ||
773 | // the entire adapted response body was produced, successfully | |
774 | void | |
775 | ServerStateData::handleAdaptedBodyProductionEnded() | |
776 | { | |
5f8252d2 | 777 | if (abortOnBadEntry("entry went bad while waiting for adapted body eof")) |
778 | return; | |
e83cdc25 A |
779 | |
780 | // end consumption if we consumed everything | |
0ad2b63b CT |
781 | if (adaptedBodySource != NULL && adaptedBodySource->exhausted()) |
782 | endAdaptedBodyConsumption(); | |
783 | // else resumeBodyStorage() will eventually consume the rest | |
784 | } | |
5f8252d2 | 785 | |
0ad2b63b CT |
786 | void |
787 | ServerStateData::endAdaptedBodyConsumption() | |
788 | { | |
789 | stopConsumingFrom(adaptedBodySource); | |
a83c6ed6 | 790 | handleAdaptationCompleted(); |
5f8252d2 | 791 | } |
792 | ||
793 | // premature end of the adapted response body | |
794 | void ServerStateData::handleAdaptedBodyProducerAborted() | |
795 | { | |
796 | stopConsumingFrom(adaptedBodySource); | |
a83c6ed6 | 797 | handleAdaptationAborted(); |
5f8252d2 | 798 | } |
799 | ||
a83c6ed6 | 800 | // common part of noteAdaptationAnswer and handleAdaptedBodyProductionEnded |
5f8252d2 | 801 | void |
a83c6ed6 | 802 | ServerStateData::handleAdaptationCompleted() |
5f8252d2 | 803 | { |
a83c6ed6 AR |
804 | debugs(11,5, HERE << "handleAdaptationCompleted"); |
805 | cleanAdaptation(); | |
bc81cb2b | 806 | |
807 | // We stop reading origin response because we have no place to put it and | |
808 | // cannot use it. If some origin servers do not like that or if we want to | |
809 | // reuse more pconns, we can add code to discard unneeded origin responses. | |
810 | if (!doneWithServer()) { | |
811 | debugs(11,3, HERE << "closing origin conn due to ICAP completion"); | |
812 | closeServer(); | |
813 | } | |
814 | ||
5f8252d2 | 815 | completeForwarding(); |
5f8252d2 | 816 | } |
817 | ||
a83c6ed6 | 818 | // common part of noteAdaptation*Aborted and noteBodyConsumerAborted methods |
5f8252d2 | 819 | void |
a83c6ed6 | 820 | ServerStateData::handleAdaptationAborted(bool bypassable) |
5f8252d2 | 821 | { |
a83c6ed6 | 822 | debugs(11,5, HERE << "handleAdaptationAborted; bypassable: " << bypassable << |
9e008dda | 823 | ", entry empty: " << entry->isEmpty()); |
5f8252d2 | 824 | |
825 | if (abortOnBadEntry("entry went bad while ICAP aborted")) | |
826 | return; | |
827 | ||
0f283edf | 828 | // TODO: bypass if possible |
829 | ||
5f8252d2 | 830 | if (entry->isEmpty()) { |
831 | debugs(11,9, HERE << "creating ICAP error entry after ICAP failure"); | |
955394ce | 832 | ErrorState *err = new ErrorState(ERR_ICAP_FAILURE, Http::scInternalServerError, request); |
129fe2a1 | 833 | err->detailError(ERR_DETAIL_ICAP_RESPMOD_EARLY); |
5f8252d2 | 834 | fwd->fail(err); |
835 | fwd->dontRetry(true); | |
b3c9f64a | 836 | } else if (request) { // update logged info directly |
64b66b76 CT |
837 | request->detailError(ERR_ICAP_FAILURE, ERR_DETAIL_ICAP_RESPMOD_LATE); |
838 | } | |
5f8252d2 | 839 | |
0f283edf | 840 | abortTransaction("ICAP failure"); |
5f8252d2 | 841 | } |
842 | ||
3af10ac0 AR |
843 | // adaptation service wants us to deny HTTP client access to this response |
844 | void | |
845 | ServerStateData::handleAdaptationBlocked(const Adaptation::Answer &answer) | |
846 | { | |
6666da11 | 847 | debugs(11,5, HERE << answer.ruleId); |
3af10ac0 AR |
848 | |
849 | if (abortOnBadEntry("entry went bad while ICAP aborted")) | |
850 | return; | |
851 | ||
852 | if (!entry->isEmpty()) { // too late to block (should not really happen) | |
ec4d1a1d A |
853 | if (request) |
854 | request->detailError(ERR_ICAP_FAILURE, ERR_DETAIL_RESPMOD_BLOCK_LATE); | |
855 | abortTransaction("late adaptation block"); | |
856 | return; | |
3af10ac0 | 857 | } |
ec4d1a1d | 858 | |
3af10ac0 AR |
859 | debugs(11,7, HERE << "creating adaptation block response"); |
860 | ||
861 | err_type page_id = | |
862 | aclGetDenyInfoPage(&Config.denyInfoList, answer.ruleId.termedBuf(), 1); | |
863 | if (page_id == ERR_NONE) | |
864 | page_id = ERR_ACCESS_DENIED; | |
865 | ||
955394ce | 866 | ErrorState *err = new ErrorState(page_id, Http::scForbidden, request); |
129fe2a1 | 867 | err->detailError(ERR_DETAIL_RESPMOD_BLOCK_EARLY); |
3af10ac0 AR |
868 | fwd->fail(err); |
869 | fwd->dontRetry(true); | |
870 | ||
871 | abortTransaction("timely adaptation block"); | |
872 | } | |
873 | ||
7c4e4e7f | 874 | void |
79628299 | 875 | ServerStateData::noteAdaptationAclCheckDone(Adaptation::ServiceGroupPointer group) |
7c4e4e7f | 876 | { |
a83c6ed6 | 877 | adaptationAccessCheckPending = false; |
7c4e4e7f | 878 | |
879 | if (abortOnBadEntry("entry went bad while waiting for ICAP ACL check")) | |
880 | return; | |
881 | ||
47416555 | 882 | // TODO: Should nonICAP and postICAP path check this on the server-side? |
883 | // That check now only happens on client-side, in processReplyAccess(). | |
884 | if (virginReply()->expectedBodyTooLarge(*request)) { | |
885 | sendBodyIsTooLargeError(); | |
886 | return; | |
887 | } | |
abd4b611 | 888 | // TODO: Should we check receivedBodyTooLarge on the server-side as well? |
47416555 | 889 | |
a22e6cd3 AR |
890 | if (!group) { |
891 | debugs(11,3, HERE << "no adapation needed"); | |
585ab260 | 892 | setFinalReply(virginReply()); |
7c4e4e7f | 893 | processReplyBody(); |
7c4e4e7f | 894 | return; |
895 | } | |
896 | ||
a22e6cd3 | 897 | startAdaptation(group, originalRequest()); |
7c4e4e7f | 898 | processReplyBody(); |
899 | } | |
0c25e715 | 900 | #endif |
7dc79973 | 901 | |
47416555 | 902 | void |
903 | ServerStateData::sendBodyIsTooLargeError() | |
904 | { | |
955394ce | 905 | ErrorState *err = new ErrorState(ERR_TOO_BIG, Http::scForbidden, request); |
47416555 | 906 | fwd->fail(err); |
907 | fwd->dontRetry(true); | |
908 | abortTransaction("Virgin body too large."); | |
909 | } | |
910 | ||
9e008dda | 911 | // TODO: when HttpStateData sends all errors to ICAP, |
585ab260 | 912 | // we should be able to move this at the end of setVirginReply(). |
7dc79973 | 913 | void |
585ab260 | 914 | ServerStateData::adaptOrFinalizeReply() |
7dc79973 | 915 | { |
a83c6ed6 | 916 | #if USE_ADAPTATION |
abd4b611 AR |
917 | // TODO: merge with client side and return void to hide the on/off logic? |
918 | // The callback can be called with a NULL service if adaptation is off. | |
919 | adaptationAccessCheckPending = Adaptation::AccessCheck::Start( | |
9e008dda | 920 | Adaptation::methodRespmod, Adaptation::pointPreCache, |
79628299 | 921 | originalRequest(), virginReply(), this); |
c30ac6ea | 922 | debugs(11,5, HERE << "adaptationAccessCheckPending=" << adaptationAccessCheckPending); |
abd4b611 | 923 | if (adaptationAccessCheckPending) |
7dc79973 | 924 | return; |
7dc79973 | 925 | #endif |
926 | ||
585ab260 | 927 | setFinalReply(virginReply()); |
7dc79973 | 928 | } |
929 | ||
bae917ac CT |
930 | /// initializes bodyBytesRead stats if needed and applies delta |
931 | void | |
932 | ServerStateData::adjustBodyBytesRead(const int64_t delta) | |
933 | { | |
934 | int64_t &bodyBytesRead = originalRequest()->hier.bodyBytesRead; | |
935 | ||
936 | // if we got here, do not log a dash even if we got nothing from the server | |
937 | if (bodyBytesRead < 0) | |
938 | bodyBytesRead = 0; | |
939 | ||
940 | bodyBytesRead += delta; // supports negative and zero deltas | |
941 | ||
942 | // check for overflows ("infinite" response?) and undeflows (a bug) | |
943 | Must(bodyBytesRead >= 0); | |
944 | } | |
945 | ||
7dc79973 | 946 | void |
bc81cb2b | 947 | ServerStateData::addVirginReplyBody(const char *data, ssize_t len) |
7dc79973 | 948 | { |
bae917ac CT |
949 | adjustBodyBytesRead(len); |
950 | ||
a83c6ed6 AR |
951 | #if USE_ADAPTATION |
952 | assert(!adaptationAccessCheckPending); // or would need to buffer while waiting | |
953 | if (startedAdaptation) { | |
bc81cb2b | 954 | adaptVirginReplyBody(data, len); |
7dc79973 | 955 | return; |
956 | } | |
7dc79973 | 957 | #endif |
bc81cb2b | 958 | storeReplyBody(data, len); |
959 | } | |
7dc79973 | 960 | |
bc81cb2b | 961 | // writes virgin or adapted reply body to store |
962 | void | |
963 | ServerStateData::storeReplyBody(const char *data, ssize_t len) | |
964 | { | |
2d1a172f | 965 | // write even if len is zero to push headers towards the client side |
7dc79973 | 966 | entry->write (StoreIOBuffer(len, currentOffset, (char*)data)); |
967 | ||
968 | currentOffset += len; | |
969 | } | |
970 | ||
52edecde | 971 | size_t ServerStateData::replyBodySpace(const MemBuf &readBuf, |
1c9605c5 | 972 | const size_t minSpace) const |
7dc79973 | 973 | { |
52edecde AJ |
974 | size_t space = readBuf.spaceSize(); // available space w/o heroic measures |
975 | if (space < minSpace) { | |
976 | const size_t maxSpace = readBuf.potentialSpaceSize(); // absolute best | |
977 | space = min(minSpace, maxSpace); // do not promise more than asked | |
978 | } | |
979 | ||
a83c6ed6 | 980 | #if USE_ADAPTATION |
7dc79973 | 981 | if (responseBodyBuffer) { |
9e008dda | 982 | return 0; // Stop reading if already overflowed waiting for ICAP to catch up |
7dc79973 | 983 | } |
984 | ||
985 | if (virginBodyDestination != NULL) { | |
986 | /* | |
987 | * BodyPipe buffer has a finite size limit. We | |
988 | * should not read more data from the network than will fit | |
989 | * into the pipe buffer or we _lose_ what did not fit if | |
990 | * the response ends sooner that BodyPipe frees up space: | |
991 | * There is no code to keep pumping data into the pipe once | |
992 | * response ends and serverComplete() is called. | |
993 | * | |
994 | * If the pipe is totally full, don't register the read handler. | |
995 | * The BodyPipe will call our noteMoreBodySpaceAvailable() method | |
996 | * when it has free space again. | |
997 | */ | |
a83c6ed6 AR |
998 | size_t adaptation_space = |
999 | virginBodyDestination->buf().potentialSpaceSize(); | |
7dc79973 | 1000 | |
a83c6ed6 | 1001 | debugs(11,9, "ServerStateData may read up to min(" << |
9e008dda | 1002 | adaptation_space << ", " << space << ") bytes"); |
7dc79973 | 1003 | |
a83c6ed6 AR |
1004 | if (adaptation_space < space) |
1005 | space = adaptation_space; | |
7dc79973 | 1006 | } |
1007 | #endif | |
1008 | ||
1009 | return space; | |
1010 | } |