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