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