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