]> git.ipfire.org Git - thirdparty/squid.git/blame - src/esi/Include.cc
Cleanup: migrate clientStreamNode to CBDATA_CLASS API
[thirdparty/squid.git] / src / esi / Include.cc
CommitLineData
924f73bc 1/*
bde978a6 2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
26ac0430 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.
924f73bc 7 */
8
bbc27441
AJ
9/* DEBUG: section 86 ESI processing */
10
582c2af2 11#include "squid.h"
454e8283 12
13/* MS Visual Studio Projects are monolithic, so we need the following
14 * #if to exclude the ESI code from compile process when not needed.
15 */
16#if (USE_SQUID_ESI == 1)
17
93da1f99 18#include "client_side.h"
602d9612 19#include "client_side_request.h"
f99c2cfe
AR
20#include "esi/Include.h"
21#include "esi/VarState.h"
924f73bc 22#include "HttpReply.h"
1c7ae5ff 23#include "log/access_log.h"
924f73bc 24
25CBDATA_CLASS_INIT (ESIStreamContext);
26
924f73bc 27/* other */
28static CSCB esiBufferRecipient;
29static CSD esiBufferDetach;
30/* esiStreamContext */
31static ESIStreamContext *ESIStreamContextNew (ESIIncludePtr);
32
33/* ESI TO CONSIDER:
34 * 1. retry failed upstream requests
35 */
36
37/* Detach from a buffering stream
38 */
39void
59a1efb2 40esiBufferDetach (clientStreamNode *node, ClientHttpRequest *http)
924f73bc 41{
42 /* Detach ourselves */
43 clientStreamDetach (node, http);
44}
45
24cf4917
AJ
46/**
47 * Write a chunk of data to a client 'socket'.
48 * If the reply is present, send the reply headers down the wire too.
49 *
50 * Pre-condition:
924f73bc 51 * The request is an internal ESI subrequest.
52 * data context is not NULL
53 * There are no more entries in the stream chain.
24cf4917 54 * The caller is responsible for creation and deletion of the Reply headers.
26ac0430 55 *
24cf4917
AJ
56 \note
57 * Bug 975, bug 1566 : delete rep; 2006/09/02: TS, #975
26ac0430 58 *
24cf4917
AJ
59 * This was causing double-deletes. Its possible that not deleting
60 * it here will cause memory leaks, but if so, this delete should
61 * not be reinstated or it will trigger bug #975 again - RBC 20060903
924f73bc 62 */
63void
2324cda2 64esiBufferRecipient (clientStreamNode *node, ClientHttpRequest *http, HttpReply *rep, StoreIOBuffer receivedData)
924f73bc 65{
66 /* Test preconditions */
67 assert (node != NULL);
68 /* ESI TODO: handle thisNode rather than asserting
26ac0430
AJ
69 * - it should only ever happen if we cause an
70 * abort and the callback chain loops back to
71 * here, so we can simply return. However, that
72 * itself shouldn't happen, so it stays as an
924f73bc 73 * assert for now. */
74 assert (cbdataReferenceValid (node));
75 assert (node->node.next == NULL);
9bd63d11 76 assert (http->getConn() == NULL);
924f73bc 77
78 ESIStreamContext::Pointer esiStream = dynamic_cast<ESIStreamContext *>(node->data.getRaw());
79 assert (esiStream.getRaw() != NULL);
80 /* If segments become more flexible, ignore thisNode */
2324cda2 81 assert (receivedData.length <= sizeof(esiStream->localbuffer->buf));
924f73bc 82 assert (!esiStream->finished);
83
24cf4917 84 debugs (86,5, HERE << "rep " << rep << " body " << receivedData.data << " len " << receivedData.length);
2324cda2 85 assert (node->readBuffer.offset == receivedData.offset || receivedData.length == 0);
924f73bc 86
87 /* trivial case */
88
89 if (http->out.offset != 0) {
90 assert(rep == NULL);
91 } else {
92 if (rep) {
9b769c67 93 if (rep->sline.status() != Http::scOkay) {
924f73bc 94 rep = NULL;
59a09b98 95 esiStream->include->includeFail (esiStream);
924f73bc 96 esiStream->finished = 1;
97 httpRequestFree (http);
98 return;
99 }
100
101#if HEADERS_LOG
102 /* should be done in the store rather than every recipient? */
103 headersLog(0, 0, http->request->method, rep);
104
105#endif
924f73bc 106 rep = NULL;
107 }
108 }
109
2324cda2 110 if (receivedData.data && receivedData.length) {
111 http->out.offset += receivedData.length;
924f73bc 112
2324cda2 113 if (receivedData.data >= esiStream->localbuffer->buf &&
114 receivedData.data < &esiStream->localbuffer->buf[sizeof(esiStream->localbuffer->buf)]) {
924f73bc 115 /* original static buffer */
116
2324cda2 117 if (receivedData.data != esiStream->localbuffer->buf) {
924f73bc 118 /* But not the start of it */
41d00cd3 119 memmove(esiStream->localbuffer->buf, receivedData.data, receivedData.length);
924f73bc 120 }
121
2324cda2 122 esiStream->localbuffer->len = receivedData.length;
924f73bc 123 } else {
124 assert (esiStream->buffer.getRaw() != NULL);
2324cda2 125 esiStream->buffer->len = receivedData.length;
924f73bc 126 }
127 }
128
129 /* EOF / Read error / aborted entry */
2324cda2 130 if (rep == NULL && receivedData.data == NULL && receivedData.length == 0) {
924f73bc 131 /* TODO: get stream status to test the entry for aborts */
24cf4917 132 debugs(86, 5, HERE << "Finished reading upstream data in subrequest");
924f73bc 133 esiStream->include->subRequestDone (esiStream, true);
134 esiStream->finished = 1;
135 httpRequestFree (http);
136 return;
137 }
138
924f73bc 139 /* after the write to the user occurs, (ie here, or in a callback)
140 * we call */
141 if (clientHttpRequestStatus(-1, http)) {
142 /* TODO: Does thisNode if block leak htto ? */
143 /* XXX when reviewing ESI this is the first place to look */
144 node->data = NULL;
145 esiStream->finished = 1;
59a09b98 146 esiStream->include->includeFail (esiStream);
924f73bc 147 return;
148 };
149
150 switch (clientStreamStatus (node, http)) {
151
152 case STREAM_UNPLANNED_COMPLETE: /* fallthru ok */
153
154 case STREAM_COMPLETE: /* ok */
bf8fe701 155 debugs(86, 3, "ESI subrequest finished OK");
924f73bc 156 esiStream->include->subRequestDone (esiStream, true);
157 esiStream->finished = 1;
158 httpRequestFree (http);
159 return;
160
161 case STREAM_FAILED:
e0236918 162 debugs(86, DBG_IMPORTANT, "ESI subrequest failed transfer");
59a09b98 163 esiStream->include->includeFail (esiStream);
924f73bc 164 esiStream->finished = 1;
165 httpRequestFree (http);
166 return;
167
168 case STREAM_NONE: {
26ac0430 169 StoreIOBuffer tempBuffer;
924f73bc 170
26ac0430
AJ
171 if (!esiStream->buffer.getRaw()) {
172 esiStream->buffer = esiStream->localbuffer;
173 }
924f73bc 174
26ac0430 175 esiStream->buffer = esiStream->buffer->tail();
924f73bc 176
26ac0430
AJ
177 if (esiStream->buffer->len) {
178 esiStream->buffer->next = new ESISegment;
179 esiStream->buffer = esiStream->buffer->next;
924f73bc 180 }
181
26ac0430
AJ
182 tempBuffer.offset = http->out.offset;
183 tempBuffer.length = sizeof (esiStream->buffer->buf);
184 tempBuffer.data = esiStream->buffer->buf;
185 /* now just read into 'buffer' */
186 clientStreamRead (node, http, tempBuffer);
187 debugs(86, 5, HERE << "Requested more data for ESI subrequest");
188 }
189
190 break;
924f73bc 191
192 default:
193 fatal ("Hit unreachable code in esiBufferRecipient\n");
194 }
195
196}
197
198/* esiStream functions */
199ESIStreamContext::~ESIStreamContext()
200{
924f73bc 201 freeResources();
202}
203
204void
205ESIStreamContext::freeResources()
206{
bf8fe701 207 debugs(86, 5, "Freeing stream context resources.");
924f73bc 208 buffer = NULL;
209 localbuffer = NULL;
210 include = NULL;
211}
212
924f73bc 213ESIStreamContext *
214ESIStreamContextNew (ESIIncludePtr include)
215{
216 ESIStreamContext *rv = new ESIStreamContext;
217 rv->include = include;
218 return rv;
219}
220
924f73bc 221/* ESIInclude */
222ESIInclude::~ESIInclude()
223{
bf8fe701 224 debugs(86, 5, "ESIInclude::Free " << this);
924f73bc 225 ESISegmentFreeList (srccontent);
226 ESISegmentFreeList (altcontent);
227 cbdataReferenceDone (varState);
228 safe_free (srcurl);
229 safe_free (alturl);
230}
231
232void
233ESIInclude::finish()
234{
235 parent = NULL;
236}
237
924f73bc 238ESIElement::Pointer
239ESIInclude::makeCacheable() const
240{
241 return new ESIInclude (*this);
242}
243
244ESIElement::Pointer
245ESIInclude::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
246{
247 ESIInclude *resultI = new ESIInclude (*this);
248 ESIElement::Pointer result = resultI;
249 resultI->parent = newParent;
250 resultI->varState = cbdataReference (&newVarState);
251
252 if (resultI->srcurl)
253 resultI->src = ESIStreamContextNew (resultI);
254
255 if (resultI->alturl)
256 resultI->alt = ESIStreamContextNew (resultI);
257
258 return result;
259}
260
a5488e5d 261ESIInclude::ESIInclude(ESIInclude const &old) :
f53969cc
SM
262 varState(NULL),
263 srcurl(NULL),
264 alturl(NULL),
265 parent(NULL),
266 started(false),
267 sent(false)
924f73bc 268{
a5488e5d 269 memset(&flags, 0, sizeof(flags));
924f73bc 270 flags.onerrorcontinue = old.flags.onerrorcontinue;
271
272 if (old.srcurl)
86c63190 273 srcurl = xstrdup(old.srcurl);
924f73bc 274
275 if (old.alturl)
86c63190 276 alturl = xstrdup(old.alturl);
924f73bc 277}
278
279void
280ESIInclude::prepareRequestHeaders(HttpHeader &tempheaders, ESIVarState *vars)
281{
924f73bc 282 tempheaders.update (&vars->header(), NULL);
3b6b6092 283 tempheaders.removeHopByHopEntries();
924f73bc 284}
285
924f73bc 286void
287ESIInclude::Start (ESIStreamContext::Pointer stream, char const *url, ESIVarState *vars)
288{
924f73bc 289 if (!stream.getRaw())
290 return;
291
818c6c9e 292 HttpHeader tempheaders(hoRequest);
293
924f73bc 294 prepareRequestHeaders(tempheaders, vars);
295
296 /* Ensure variable state is clean */
297 vars->feedData(url, strlen (url));
298
299 /* tempUrl is eaten by the request */
300 char const *tempUrl = vars->extractChar ();
301
c2a7cefd 302 debugs(86, 5, "ESIIncludeStart: Starting subrequest with url '" << tempUrl << "'");
924f73bc 303
c2a7cefd 304 if (clientBeginRequest(Http::METHOD_GET, tempUrl, esiBufferRecipient, esiBufferDetach, stream.getRaw(), &tempheaders, stream->localbuffer->buf, HTTP_REQBUF_SZ)) {
fa84c01d 305 debugs(86, DBG_CRITICAL, "starting new ESI subrequest failed");
924f73bc 306 }
307
519e0948 308 tempheaders.clean();
924f73bc 309}
310
a5488e5d 311ESIInclude::ESIInclude(esiTreeParentPtr aParent, int attrcount, char const **attr, ESIContext *aContext) :
f53969cc
SM
312 varState(NULL),
313 srcurl(NULL),
314 alturl(NULL),
315 parent(aParent),
316 started(false),
317 sent(false)
924f73bc 318{
924f73bc 319 assert (aContext);
a5488e5d 320 memset(&flags, 0, sizeof(flags));
924f73bc 321
a5488e5d 322 for (int i = 0; i < attrcount && attr[i]; i += 2) {
924f73bc 323 if (!strcmp(attr[i],"src")) {
324 /* Start a request for thisNode url */
bf8fe701 325 debugs(86, 5, "ESIIncludeNew: Requesting source '" << attr[i+1] << "'");
326
924f73bc 327 /* TODO: don't assert on thisNode, ignore the duplicate */
328 assert (src.getRaw() == NULL);
329 src = ESIStreamContextNew (this);
330 assert (src.getRaw() != NULL);
86c63190 331 srcurl = xstrdup(attr[i+1]);
924f73bc 332 } else if (!strcmp(attr[i],"alt")) {
333 /* Start a secondary request for thisNode url */
334 /* TODO: make a config parameter to wait on requesting alt's
335 * for the src to fail
336 */
bf8fe701 337 debugs(86, 5, "ESIIncludeNew: Requesting alternate '" << attr[i+1] << "'");
338
924f73bc 339 assert (alt.getRaw() == NULL); /* TODO: FIXME */
340 alt = ESIStreamContextNew (this);
341 assert (alt.getRaw() != NULL);
86c63190 342 alturl = xstrdup(attr[i+1]);
924f73bc 343 } else if (!strcmp(attr[i],"onerror")) {
344 if (!strcmp(attr[i+1], "continue")) {
345 flags.onerrorcontinue = 1;
346 } else {
347 /* ignore mistyped attributes */
e0236918 348 debugs(86, DBG_IMPORTANT, "invalid value for onerror='" << attr[i+1] << "'");
924f73bc 349 }
350 } else {
351 /* ignore mistyped attributes. TODO:? error on these for user feedback - config parameter needed
352 */
353 }
354 }
355
356 varState = cbdataReference(aContext->varState);
357}
358
359void
360ESIInclude::start()
361{
362 /* prevent freeing ourselves */
363 ESIIncludePtr foo(this);
364
365 if (started)
366 return;
367
368 started = true;
369
370 if (src.getRaw()) {
371 Start (src, srcurl, varState);
372 Start (alt, alturl, varState);
373 } else {
374 alt = NULL;
375
e0236918 376 debugs(86, DBG_IMPORTANT, "ESIIncludeNew: esi:include with no src attributes");
924f73bc 377
378 flags.failed = 1;
379 }
380}
381
382void
383ESIInclude::render(ESISegment::Pointer output)
384{
385 if (sent)
386 return;
387
388 ESISegment::Pointer myout;
389
bf8fe701 390 debugs(86, 5, "ESIIncludeRender: Rendering include " << this);
924f73bc 391
392 assert (flags.finished || (flags.failed && flags.onerrorcontinue));
393
394 if (flags.failed && flags.onerrorcontinue) {
395 return;
396 }
397
398 /* Render the content */
399 if (srccontent.getRaw()) {
400 myout = srccontent;
401 srccontent = NULL;
402 } else if (altcontent.getRaw()) {
403 myout = altcontent;
404 altcontent = NULL;
405 } else
406 fatal ("ESIIncludeRender called with no content, and no failure!\n");
407
408 assert (output->next == NULL);
409
410 output->next = myout;
411
412 sent = true;
413}
414
415esiProcessResult_t
416ESIInclude::process (int dovars)
417{
418 /* Prevent refcount race leading to free */
419 Pointer me (this);
420 start();
bf8fe701 421 debugs(86, 5, "ESIIncludeRender: Processing include " << this);
924f73bc 422
423 if (flags.failed) {
424 if (flags.onerrorcontinue)
425 return ESI_PROCESS_COMPLETE;
426 else
427 return ESI_PROCESS_FAILED;
428 }
429
430 if (!flags.finished) {
431 if (flags.onerrorcontinue)
432 return ESI_PROCESS_PENDING_WONTFAIL;
433 else
434 return ESI_PROCESS_PENDING_MAYFAIL;
435 }
436
437 return ESI_PROCESS_COMPLETE;
438}
439
440void
59a09b98 441ESIInclude::includeFail (ESIStreamContext::Pointer stream)
924f73bc 442{
443 subRequestDone (stream, false);
444}
445
446bool
447ESIInclude::dataNeeded() const
448{
449 return !(flags.finished || flags.failed);
450}
451
452void
453ESIInclude::subRequestDone (ESIStreamContext::Pointer stream, bool success)
454{
924f73bc 455 if (!dataNeeded())
456 return;
457
458 if (stream == src) {
bf8fe701 459 debugs(86, 3, "ESIInclude::subRequestDone: " << srcurl);
924f73bc 460
461 if (success) {
462 /* copy the lead segment */
bf8fe701 463 debugs(86, 3, "ESIIncludeSubRequestDone: Src OK - include PASSED.");
924f73bc 464 assert (!srccontent.getRaw());
465 ESISegment::ListTransfer (stream->localbuffer, srccontent);
466 /* we're done! */
467 flags.finished = 1;
468 } else {
469 /* Fail if there is no alt being retrieved */
bf8fe701 470 debugs(86, 3, "ESIIncludeSubRequestDone: Src FAILED");
924f73bc 471
472 if (!(alt.getRaw() || altcontent.getRaw())) {
bf8fe701 473 debugs(86, 3, "ESIIncludeSubRequestDone: Include FAILED - No ALT");
924f73bc 474 flags.failed = 1;
475 } else if (altcontent.getRaw()) {
bf8fe701 476 debugs(86, 3, "ESIIncludeSubRequestDone: Include PASSED - ALT already Complete");
924f73bc 477 /* ALT was already retrieved, we are done */
478 flags.finished = 1;
479 }
480 }
481
482 src = NULL;
483 } else if (stream == alt) {
bf8fe701 484 debugs(86, 3, "ESIInclude::subRequestDone: " << alturl);
924f73bc 485
486 if (success) {
bf8fe701 487 debugs(86, 3, "ESIIncludeSubRequestDone: ALT OK.");
924f73bc 488 /* copy the lead segment */
489 assert (!altcontent.getRaw());
490 ESISegment::ListTransfer (stream->localbuffer, altcontent);
491 /* we're done! */
492
493 if (!(src.getRaw() || srccontent.getRaw())) {
494 /* src already failed, kick ESI processor */
bf8fe701 495 debugs(86, 3, "ESIIncludeSubRequestDone: Include PASSED - SRC already failed.");
924f73bc 496 flags.finished = 1;
497 }
498 } else {
499 if (!(src.getRaw() || srccontent.getRaw())) {
bf8fe701 500 debugs(86, 3, "ESIIncludeSubRequestDone: ALT FAILED, Include FAILED - SRC already failed");
924f73bc 501 /* src already failed */
502 flags.failed = 1;
503 }
504 }
505
506 alt = NULL;
507 } else {
508 fatal ("ESIIncludeSubRequestDone: non-owned stream found!\n");
509 }
510
511 if (flags.finished || flags.failed) {
512 /* Kick ESI Processor */
26ac0430
AJ
513 debugs (86, 5, "ESIInclude " << this <<
514 " SubRequest " << stream.getRaw() <<
2a2d937f 515 " completed, kicking processor , status " <<
516 (flags.finished ? "OK" : "FAILED"));
517 /* There is a race condition - and we have no reproducible test case -
26ac0430 518 * during a subrequest the parent will get set to NULL, which is not
2a2d937f 519 * meant to be possible. Rather than killing squid, we let it leak
520 * memory but complain in the log.
521 *
522 * Someone wanting to debug this could well start by running squid with
523 * a hardware breakpoint set to this location.
524 * Its probably due to parent being set to null - by a call to
525 * 'this.finish' while the subrequest is still not completed.
526 */
527 if (parent.getRaw() == NULL) {
528 debugs (86, 0, "ESIInclude::subRequestDone: Sub request completed "
26ac0430
AJ
529 "after finish() called and parent unlinked. Unable to "
530 "continue handling the request, and may be memory leaking. "
531 "See http://www.squid-cache.org/bugs/show_bug.cgi?id=951 - we "
532 "are looking for a reproducible test case. This will require "
533 "an ESI template with includes, probably with alt-options, "
534 "and we're likely to need traffic dumps to allow us to "
535 "reconstruct the exact tcp handling sequences to trigger this "
536 "rather elusive bug.");
2a2d937f 537 return;
538 }
924f73bc 539 assert (parent.getRaw());
540
541 if (!flags.failed) {
542 sent = true;
543 parent->provideData (srccontent.getRaw() ? srccontent:altcontent,this);
544
545 if (srccontent.getRaw())
546 srccontent = NULL;
547 else
548 altcontent = NULL;
549 } else if (flags.onerrorcontinue) {
550 /* render nothing but inform of completion */
551
552 if (!sent) {
553 sent = true;
554 parent->provideData (new ESISegment, this);
555 } else
556 assert (0);
557 } else
558 parent->fail(this, "esi:include could not be completed.");
559 }
560}
561
454e8283 562#endif /* USE_SQUID_ESI == 1 */
f53969cc 563