3 * DEBUG: section 86 ESI processing
4 * AUTHOR: Robert Collins
6 * SQUID Web Proxy Cache http://www.squid-cache.org/
7 * ----------------------------------------------------------
9 * Squid is the result of efforts by numerous individuals from
10 * the Internet community; see the CONTRIBUTORS file for full
11 * details. Many organizations have provided support for Squid's
12 * development; see the SPONSORS file for full details. Squid is
13 * Copyrighted (C) 2001 by the Regents of the University of
14 * California; see the COPYRIGHT file for full details. Squid
15 * incorporates software developed and/or copyrighted by other
16 * sources; see the CREDITS file for full details.
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 ; but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
32 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
37 /* MS Visual Studio Projects are monolithic, so we need the following
38 * #if to exclude the ESI code from compile process when not needed.
40 #if (USE_SQUID_ESI == 1)
42 #include "client_side.h"
43 #include "client_side_request.h"
44 #include "esi/Include.h"
45 #include "esi/VarState.h"
46 #include "HttpReply.h"
47 #include "log/access_log.h"
49 CBDATA_CLASS_INIT (ESIStreamContext
);
52 static CSCB esiBufferRecipient
;
53 static CSD esiBufferDetach
;
54 /* esiStreamContext */
55 static ESIStreamContext
*ESIStreamContextNew (ESIIncludePtr
);
58 * 1. retry failed upstream requests
61 /* Detach from a buffering stream
64 esiBufferDetach (clientStreamNode
*node
, ClientHttpRequest
*http
)
66 /* Detach ourselves */
67 clientStreamDetach (node
, http
);
71 * Write a chunk of data to a client 'socket'.
72 * If the reply is present, send the reply headers down the wire too.
75 * The request is an internal ESI subrequest.
76 * data context is not NULL
77 * There are no more entries in the stream chain.
78 * The caller is responsible for creation and deletion of the Reply headers.
81 * Bug 975, bug 1566 : delete rep; 2006/09/02: TS, #975
83 * This was causing double-deletes. Its possible that not deleting
84 * it here will cause memory leaks, but if so, this delete should
85 * not be reinstated or it will trigger bug #975 again - RBC 20060903
88 esiBufferRecipient (clientStreamNode
*node
, ClientHttpRequest
*http
, HttpReply
*rep
, StoreIOBuffer receivedData
)
90 /* Test preconditions */
91 assert (node
!= NULL
);
92 /* ESI TODO: handle thisNode rather than asserting
93 * - it should only ever happen if we cause an
94 * abort and the callback chain loops back to
95 * here, so we can simply return. However, that
96 * itself shouldn't happen, so it stays as an
98 assert (cbdataReferenceValid (node
));
99 assert (node
->node
.next
== NULL
);
100 assert (http
->getConn() == NULL
);
102 ESIStreamContext::Pointer esiStream
= dynamic_cast<ESIStreamContext
*>(node
->data
.getRaw());
103 assert (esiStream
.getRaw() != NULL
);
104 /* If segments become more flexible, ignore thisNode */
105 assert (receivedData
.length
<= sizeof(esiStream
->localbuffer
->buf
));
106 assert (!esiStream
->finished
);
108 debugs (86,5, HERE
<< "rep " << rep
<< " body " << receivedData
.data
<< " len " << receivedData
.length
);
109 assert (node
->readBuffer
.offset
== receivedData
.offset
|| receivedData
.length
== 0);
113 if (http
->out
.offset
!= 0) {
117 if (rep
->sline
.status() != Http::scOkay
) {
119 esiStream
->include
->includeFail (esiStream
);
120 esiStream
->finished
= 1;
121 httpRequestFree (http
);
126 /* should be done in the store rather than every recipient? */
127 headersLog(0, 0, http
->request
->method
, rep
);
134 if (receivedData
.data
&& receivedData
.length
) {
135 http
->out
.offset
+= receivedData
.length
;
137 if (receivedData
.data
>= esiStream
->localbuffer
->buf
&&
138 receivedData
.data
< &esiStream
->localbuffer
->buf
[sizeof(esiStream
->localbuffer
->buf
)]) {
139 /* original static buffer */
141 if (receivedData
.data
!= esiStream
->localbuffer
->buf
) {
142 /* But not the start of it */
143 memmove(esiStream
->localbuffer
->buf
, receivedData
.data
, receivedData
.length
);
146 esiStream
->localbuffer
->len
= receivedData
.length
;
148 assert (esiStream
->buffer
.getRaw() != NULL
);
149 esiStream
->buffer
->len
= receivedData
.length
;
153 /* EOF / Read error / aborted entry */
154 if (rep
== NULL
&& receivedData
.data
== NULL
&& receivedData
.length
== 0) {
155 /* TODO: get stream status to test the entry for aborts */
156 debugs(86, 5, HERE
<< "Finished reading upstream data in subrequest");
157 esiStream
->include
->subRequestDone (esiStream
, true);
158 esiStream
->finished
= 1;
159 httpRequestFree (http
);
163 /* after the write to the user occurs, (ie here, or in a callback)
165 if (clientHttpRequestStatus(-1, http
)) {
166 /* TODO: Does thisNode if block leak htto ? */
167 /* XXX when reviewing ESI this is the first place to look */
169 esiStream
->finished
= 1;
170 esiStream
->include
->includeFail (esiStream
);
174 switch (clientStreamStatus (node
, http
)) {
176 case STREAM_UNPLANNED_COMPLETE
: /* fallthru ok */
178 case STREAM_COMPLETE
: /* ok */
179 debugs(86, 3, "ESI subrequest finished OK");
180 esiStream
->include
->subRequestDone (esiStream
, true);
181 esiStream
->finished
= 1;
182 httpRequestFree (http
);
186 debugs(86, DBG_IMPORTANT
, "ESI subrequest failed transfer");
187 esiStream
->include
->includeFail (esiStream
);
188 esiStream
->finished
= 1;
189 httpRequestFree (http
);
193 StoreIOBuffer tempBuffer
;
195 if (!esiStream
->buffer
.getRaw()) {
196 esiStream
->buffer
= esiStream
->localbuffer
;
199 esiStream
->buffer
= esiStream
->buffer
->tail();
201 if (esiStream
->buffer
->len
) {
202 esiStream
->buffer
->next
= new ESISegment
;
203 esiStream
->buffer
= esiStream
->buffer
->next
;
206 tempBuffer
.offset
= http
->out
.offset
;
207 tempBuffer
.length
= sizeof (esiStream
->buffer
->buf
);
208 tempBuffer
.data
= esiStream
->buffer
->buf
;
209 /* now just read into 'buffer' */
210 clientStreamRead (node
, http
, tempBuffer
);
211 debugs(86, 5, HERE
<< "Requested more data for ESI subrequest");
217 fatal ("Hit unreachable code in esiBufferRecipient\n");
222 /* esiStream functions */
223 ESIStreamContext::~ESIStreamContext()
230 ESIStreamContext::freeResources()
232 debugs(86, 5, "Freeing stream context resources.");
239 ESIStreamContextNew (ESIIncludePtr include
)
241 ESIStreamContext
*rv
= new ESIStreamContext
;
242 rv
->include
= include
;
247 ESIInclude::~ESIInclude()
249 debugs(86, 5, "ESIInclude::Free " << this);
250 ESISegmentFreeList (srccontent
);
251 ESISegmentFreeList (altcontent
);
252 cbdataReferenceDone (varState
);
264 ESIInclude::makeCacheable() const
266 return new ESIInclude (*this);
270 ESIInclude::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
272 ESIInclude
*resultI
= new ESIInclude (*this);
273 ESIElement::Pointer result
= resultI
;
274 resultI
->parent
= newParent
;
275 resultI
->varState
= cbdataReference (&newVarState
);
278 resultI
->src
= ESIStreamContextNew (resultI
);
281 resultI
->alt
= ESIStreamContextNew (resultI
);
286 ESIInclude::ESIInclude(ESIInclude
const &old
) :
294 memset(&flags
, 0, sizeof(flags
));
295 flags
.onerrorcontinue
= old
.flags
.onerrorcontinue
;
298 srcurl
= xstrdup (old
.srcurl
);
301 alturl
= xstrdup (old
.alturl
);
305 ESIInclude::prepareRequestHeaders(HttpHeader
&tempheaders
, ESIVarState
*vars
)
307 tempheaders
.update (&vars
->header(), NULL
);
308 tempheaders
.removeHopByHopEntries();
312 ESIInclude::Start (ESIStreamContext::Pointer stream
, char const *url
, ESIVarState
*vars
)
314 if (!stream
.getRaw())
317 HttpHeader
tempheaders(hoRequest
);
319 prepareRequestHeaders(tempheaders
, vars
);
321 /* Ensure variable state is clean */
322 vars
->feedData(url
, strlen (url
));
324 /* tempUrl is eaten by the request */
325 char const *tempUrl
= vars
->extractChar ();
327 debugs(86, 5, "ESIIncludeStart: Starting subrequest with url '" << tempUrl
<< "'");
329 if (clientBeginRequest(Http::METHOD_GET
, tempUrl
, esiBufferRecipient
, esiBufferDetach
, stream
.getRaw(), &tempheaders
, stream
->localbuffer
->buf
, HTTP_REQBUF_SZ
)) {
330 debugs(86, DBG_CRITICAL
, "starting new ESI subrequest failed");
336 ESIInclude::ESIInclude(esiTreeParentPtr aParent
, int attrcount
, char const **attr
, ESIContext
*aContext
) :
345 memset(&flags
, 0, sizeof(flags
));
347 for (int i
= 0; i
< attrcount
&& attr
[i
]; i
+= 2) {
348 if (!strcmp(attr
[i
],"src")) {
349 /* Start a request for thisNode url */
350 debugs(86, 5, "ESIIncludeNew: Requesting source '" << attr
[i
+1] << "'");
352 /* TODO: don't assert on thisNode, ignore the duplicate */
353 assert (src
.getRaw() == NULL
);
354 src
= ESIStreamContextNew (this);
355 assert (src
.getRaw() != NULL
);
356 srcurl
= xstrdup ( attr
[i
+1]);
357 } else if (!strcmp(attr
[i
],"alt")) {
358 /* Start a secondary request for thisNode url */
359 /* TODO: make a config parameter to wait on requesting alt's
360 * for the src to fail
362 debugs(86, 5, "ESIIncludeNew: Requesting alternate '" << attr
[i
+1] << "'");
364 assert (alt
.getRaw() == NULL
); /* TODO: FIXME */
365 alt
= ESIStreamContextNew (this);
366 assert (alt
.getRaw() != NULL
);
367 alturl
= xstrdup (attr
[i
+1]);
368 } else if (!strcmp(attr
[i
],"onerror")) {
369 if (!strcmp(attr
[i
+1], "continue")) {
370 flags
.onerrorcontinue
= 1;
372 /* ignore mistyped attributes */
373 debugs(86, DBG_IMPORTANT
, "invalid value for onerror='" << attr
[i
+1] << "'");
376 /* ignore mistyped attributes. TODO:? error on these for user feedback - config parameter needed
381 varState
= cbdataReference(aContext
->varState
);
387 /* prevent freeing ourselves */
388 ESIIncludePtr
foo(this);
396 Start (src
, srcurl
, varState
);
397 Start (alt
, alturl
, varState
);
401 debugs(86, DBG_IMPORTANT
, "ESIIncludeNew: esi:include with no src attributes");
408 ESIInclude::render(ESISegment::Pointer output
)
413 ESISegment::Pointer myout
;
415 debugs(86, 5, "ESIIncludeRender: Rendering include " << this);
417 assert (flags
.finished
|| (flags
.failed
&& flags
.onerrorcontinue
));
419 if (flags
.failed
&& flags
.onerrorcontinue
) {
423 /* Render the content */
424 if (srccontent
.getRaw()) {
427 } else if (altcontent
.getRaw()) {
431 fatal ("ESIIncludeRender called with no content, and no failure!\n");
433 assert (output
->next
== NULL
);
435 output
->next
= myout
;
441 ESIInclude::process (int dovars
)
443 /* Prevent refcount race leading to free */
446 debugs(86, 5, "ESIIncludeRender: Processing include " << this);
449 if (flags
.onerrorcontinue
)
450 return ESI_PROCESS_COMPLETE
;
452 return ESI_PROCESS_FAILED
;
455 if (!flags
.finished
) {
456 if (flags
.onerrorcontinue
)
457 return ESI_PROCESS_PENDING_WONTFAIL
;
459 return ESI_PROCESS_PENDING_MAYFAIL
;
462 return ESI_PROCESS_COMPLETE
;
466 ESIInclude::includeFail (ESIStreamContext::Pointer stream
)
468 subRequestDone (stream
, false);
472 ESIInclude::dataNeeded() const
474 return !(flags
.finished
|| flags
.failed
);
478 ESIInclude::subRequestDone (ESIStreamContext::Pointer stream
, bool success
)
486 debugs(86, 3, "ESIInclude::subRequestDone: " << srcurl
);
489 /* copy the lead segment */
490 debugs(86, 3, "ESIIncludeSubRequestDone: Src OK - include PASSED.");
491 assert (!srccontent
.getRaw());
492 ESISegment::ListTransfer (stream
->localbuffer
, srccontent
);
496 /* Fail if there is no alt being retrieved */
497 debugs(86, 3, "ESIIncludeSubRequestDone: Src FAILED");
499 if (!(alt
.getRaw() || altcontent
.getRaw())) {
500 debugs(86, 3, "ESIIncludeSubRequestDone: Include FAILED - No ALT");
502 } else if (altcontent
.getRaw()) {
503 debugs(86, 3, "ESIIncludeSubRequestDone: Include PASSED - ALT already Complete");
504 /* ALT was already retrieved, we are done */
510 } else if (stream
== alt
) {
511 debugs(86, 3, "ESIInclude::subRequestDone: " << alturl
);
514 debugs(86, 3, "ESIIncludeSubRequestDone: ALT OK.");
515 /* copy the lead segment */
516 assert (!altcontent
.getRaw());
517 ESISegment::ListTransfer (stream
->localbuffer
, altcontent
);
520 if (!(src
.getRaw() || srccontent
.getRaw())) {
521 /* src already failed, kick ESI processor */
522 debugs(86, 3, "ESIIncludeSubRequestDone: Include PASSED - SRC already failed.");
526 if (!(src
.getRaw() || srccontent
.getRaw())) {
527 debugs(86, 3, "ESIIncludeSubRequestDone: ALT FAILED, Include FAILED - SRC already failed");
528 /* src already failed */
535 fatal ("ESIIncludeSubRequestDone: non-owned stream found!\n");
538 if (flags
.finished
|| flags
.failed
) {
539 /* Kick ESI Processor */
540 debugs (86, 5, "ESIInclude " << this <<
541 " SubRequest " << stream
.getRaw() <<
542 " completed, kicking processor , status " <<
543 (flags
.finished
? "OK" : "FAILED"));
544 /* There is a race condition - and we have no reproducible test case -
545 * during a subrequest the parent will get set to NULL, which is not
546 * meant to be possible. Rather than killing squid, we let it leak
547 * memory but complain in the log.
549 * Someone wanting to debug this could well start by running squid with
550 * a hardware breakpoint set to this location.
551 * Its probably due to parent being set to null - by a call to
552 * 'this.finish' while the subrequest is still not completed.
554 if (parent
.getRaw() == NULL
) {
555 debugs (86, 0, "ESIInclude::subRequestDone: Sub request completed "
556 "after finish() called and parent unlinked. Unable to "
557 "continue handling the request, and may be memory leaking. "
558 "See http://www.squid-cache.org/bugs/show_bug.cgi?id=951 - we "
559 "are looking for a reproducible test case. This will require "
560 "an ESI template with includes, probably with alt-options, "
561 "and we're likely to need traffic dumps to allow us to "
562 "reconstruct the exact tcp handling sequences to trigger this "
563 "rather elusive bug.");
566 assert (parent
.getRaw());
570 parent
->provideData (srccontent
.getRaw() ? srccontent
:altcontent
,this);
572 if (srccontent
.getRaw())
576 } else if (flags
.onerrorcontinue
) {
577 /* render nothing but inform of completion */
581 parent
->provideData (new ESISegment
, this);
585 parent
->fail(this, "esi:include could not be completed.");
589 #endif /* USE_SQUID_ESI == 1 */