3
* $Id: ESIInclude.cc,v 1.14.2.2 2008/02/27 05:59:29 amosjeffries Exp $
5
* DEBUG: section 86 ESI processing
6
* AUTHOR: Robert Collins
8
* SQUID Web Proxy Cache http://www.squid-cache.org/
9
* ----------------------------------------------------------
11
* Squid is the result of efforts by numerous individuals from
12
* the Internet community; see the CONTRIBUTORS file for full
13
* details. Many organizations have provided support for Squid's
14
* development; see the SPONSORS file for full details. Squid is
15
* Copyrighted (C) 2001 by the Regents of the University of
16
* California; see the COPYRIGHT file for full details. Squid
17
* incorporates software developed and/or copyrighted by other
18
* sources; see the CREDITS file for full details.
20
* This program is free software; you can redistribute it and/or modify
21
* it under the terms of the GNU General Public License as published by
22
* the Free Software Foundation; either version 2 of the License, or
23
* (at your option) any later version.
25
* This program is distributed in the hope that it will be useful,
26
; but WITHOUT ANY WARRANTY; without even the implied warranty of
27
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
* GNU General Public License for more details.
30
* You should have received a copy of the GNU General Public License
31
* along with this program; if not, write to the Free Software
32
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
34
* Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
39
/* MS Visual Studio Projects are monolithic, so we need the following
40
* #if to exclude the ESI code from compile process when not needed.
42
#if (USE_SQUID_ESI == 1)
44
#include "ESIInclude.h"
45
#include "ESIVarState.h"
46
#include "client_side_request.h"
47
#include "HttpReply.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().getRaw() == 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_OK) {
119
esiStream->include->fail (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
xmemmove (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);
164
/* after the write to the user occurs, (ie here, or in a callback)
166
if (clientHttpRequestStatus(-1, http)) {
167
/* TODO: Does thisNode if block leak htto ? */
168
/* XXX when reviewing ESI this is the first place to look */
170
esiStream->finished = 1;
171
esiStream->include->fail (esiStream);
175
switch (clientStreamStatus (node, http)) {
177
case STREAM_UNPLANNED_COMPLETE: /* fallthru ok */
179
case STREAM_COMPLETE: /* ok */
180
debugs(86, 3, "ESI subrequest finished OK");
181
esiStream->include->subRequestDone (esiStream, true);
182
esiStream->finished = 1;
183
httpRequestFree (http);
187
debugs(86, 1, "ESI subrequest failed transfer");
188
esiStream->include->fail (esiStream);
189
esiStream->finished = 1;
190
httpRequestFree (http);
194
StoreIOBuffer tempBuffer;
196
if (!esiStream->buffer.getRaw()) {
197
esiStream->buffer = esiStream->localbuffer;
200
esiStream->buffer = esiStream->buffer->tail();
202
if (esiStream->buffer->len) {
203
esiStream->buffer->next = new ESISegment;
204
esiStream->buffer = esiStream->buffer->next;
207
tempBuffer.offset = http->out.offset;
208
tempBuffer.length = sizeof (esiStream->buffer->buf);
209
tempBuffer.data = esiStream->buffer->buf;
210
/* now just read into 'buffer' */
211
clientStreamRead (node, http, tempBuffer);
212
debugs(86, 5, HERE << "Requested more data for ESI subrequest");
218
fatal ("Hit unreachable code in esiBufferRecipient\n");
223
/* esiStream functions */
224
ESIStreamContext::~ESIStreamContext()
231
ESIStreamContext::freeResources()
233
debugs(86, 5, "Freeing stream context resources.");
240
ESIStreamContext::operator new(size_t byteCount)
242
assert (byteCount == sizeof (ESIStreamContext));
243
CBDATA_INIT_TYPE(ESIStreamContext);
244
ESIStreamContext *result = cbdataAlloc(ESIStreamContext);
249
ESIStreamContext::operator delete (void *address)
251
ESIStreamContext *t = static_cast<ESIStreamContext *>(address);
256
ESIStreamContextNew (ESIIncludePtr include)
258
ESIStreamContext *rv = new ESIStreamContext;
259
rv->include = include;
266
ESIInclude::~ESIInclude()
268
debugs(86, 5, "ESIInclude::Free " << this);
269
ESISegmentFreeList (srccontent);
270
ESISegmentFreeList (altcontent);
271
cbdataReferenceDone (varState);
283
ESIInclude::makeCacheable() const
285
return new ESIInclude (*this);
289
ESIInclude::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
291
ESIInclude *resultI = new ESIInclude (*this);
292
ESIElement::Pointer result = resultI;
293
resultI->parent = newParent;
294
resultI->varState = cbdataReference (&newVarState);
297
resultI->src = ESIStreamContextNew (resultI);
300
resultI->alt = ESIStreamContextNew (resultI);
305
ESIInclude::ESIInclude(ESIInclude const &old) : parent (NULL), started (false), sent (false)
308
flags.onerrorcontinue = old.flags.onerrorcontinue;
311
srcurl = xstrdup (old.srcurl);
314
alturl = xstrdup (old.alturl);
318
ESIInclude::prepareRequestHeaders(HttpHeader &tempheaders, ESIVarState *vars)
320
tempheaders.update (&vars->header(), NULL);
321
tempheaders.removeHopByHopEntries();
326
ESIInclude::Start (ESIStreamContext::Pointer stream, char const *url, ESIVarState *vars)
328
if (!stream.getRaw())
331
HttpHeader tempheaders(hoRequest);
333
prepareRequestHeaders(tempheaders, vars);
335
/* Ensure variable state is clean */
336
vars->feedData(url, strlen (url));
338
/* tempUrl is eaten by the request */
339
char const *tempUrl = vars->extractChar ();
341
debugs(86, 5, "ESIIncludeStart: Starting subrequest with url '" << tempUrl <<
344
if (clientBeginRequest(METHOD_GET, tempUrl, esiBufferRecipient, esiBufferDetach, stream.getRaw(), &tempheaders, stream->localbuffer->buf, HTTP_REQBUF_SZ)) {
345
debugs(86, 0, "starting new ESI subrequest failed");
351
ESIInclude::ESIInclude (esiTreeParentPtr aParent, int attrcount, char const **attr, ESIContext *aContext) : parent (aParent), started (false), sent (false)
356
for (i = 0; i < attrcount && attr[i]; i += 2) {
357
if (!strcmp(attr[i],"src")) {
358
/* Start a request for thisNode url */
359
debugs(86, 5, "ESIIncludeNew: Requesting source '" << attr[i+1] << "'");
361
/* TODO: don't assert on thisNode, ignore the duplicate */
362
assert (src.getRaw() == NULL);
363
src = ESIStreamContextNew (this);
364
assert (src.getRaw() != NULL);
365
srcurl = xstrdup ( attr[i+1]);
366
} else if (!strcmp(attr[i],"alt")) {
367
/* Start a secondary request for thisNode url */
368
/* TODO: make a config parameter to wait on requesting alt's
369
* for the src to fail
371
debugs(86, 5, "ESIIncludeNew: Requesting alternate '" << attr[i+1] << "'");
373
assert (alt.getRaw() == NULL); /* TODO: FIXME */
374
alt = ESIStreamContextNew (this);
375
assert (alt.getRaw() != NULL);
376
alturl = xstrdup (attr[i+1]);
377
} else if (!strcmp(attr[i],"onerror")) {
378
if (!strcmp(attr[i+1], "continue")) {
379
flags.onerrorcontinue = 1;
381
/* ignore mistyped attributes */
382
debugs(86, 1, "invalid value for onerror='" << attr[i+1] << "'");
385
/* ignore mistyped attributes. TODO:? error on these for user feedback - config parameter needed
390
varState = cbdataReference(aContext->varState);
396
/* prevent freeing ourselves */
397
ESIIncludePtr foo(this);
405
Start (src, srcurl, varState);
406
Start (alt, alturl, varState);
410
debugs(86, 1, "ESIIncludeNew: esi:include with no src attributes");
417
ESIInclude::render(ESISegment::Pointer output)
422
ESISegment::Pointer myout;
424
debugs(86, 5, "ESIIncludeRender: Rendering include " << this);
426
assert (flags.finished || (flags.failed && flags.onerrorcontinue));
428
if (flags.failed && flags.onerrorcontinue) {
432
/* Render the content */
433
if (srccontent.getRaw()) {
436
} else if (altcontent.getRaw()) {
440
fatal ("ESIIncludeRender called with no content, and no failure!\n");
442
assert (output->next == NULL);
444
output->next = myout;
450
ESIInclude::process (int dovars)
452
/* Prevent refcount race leading to free */
455
debugs(86, 5, "ESIIncludeRender: Processing include " << this);
458
if (flags.onerrorcontinue)
459
return ESI_PROCESS_COMPLETE;
461
return ESI_PROCESS_FAILED;
464
if (!flags.finished) {
465
if (flags.onerrorcontinue)
466
return ESI_PROCESS_PENDING_WONTFAIL;
468
return ESI_PROCESS_PENDING_MAYFAIL;
471
return ESI_PROCESS_COMPLETE;
475
ESIInclude::fail (ESIStreamContext::Pointer stream)
477
subRequestDone (stream, false);
481
ESIInclude::dataNeeded() const
483
return !(flags.finished || flags.failed);
487
ESIInclude::subRequestDone (ESIStreamContext::Pointer stream, bool success)
495
debugs(86, 3, "ESIInclude::subRequestDone: " << srcurl);
498
/* copy the lead segment */
499
debugs(86, 3, "ESIIncludeSubRequestDone: Src OK - include PASSED.");
500
assert (!srccontent.getRaw());
501
ESISegment::ListTransfer (stream->localbuffer, srccontent);
505
/* Fail if there is no alt being retrieved */
506
debugs(86, 3, "ESIIncludeSubRequestDone: Src FAILED");
508
if (!(alt.getRaw() || altcontent.getRaw())) {
509
debugs(86, 3, "ESIIncludeSubRequestDone: Include FAILED - No ALT");
511
} else if (altcontent.getRaw()) {
512
debugs(86, 3, "ESIIncludeSubRequestDone: Include PASSED - ALT already Complete");
513
/* ALT was already retrieved, we are done */
519
} else if (stream == alt) {
520
debugs(86, 3, "ESIInclude::subRequestDone: " << alturl);
523
debugs(86, 3, "ESIIncludeSubRequestDone: ALT OK.");
524
/* copy the lead segment */
525
assert (!altcontent.getRaw());
526
ESISegment::ListTransfer (stream->localbuffer, altcontent);
529
if (!(src.getRaw() || srccontent.getRaw())) {
530
/* src already failed, kick ESI processor */
531
debugs(86, 3, "ESIIncludeSubRequestDone: Include PASSED - SRC already failed.");
535
if (!(src.getRaw() || srccontent.getRaw())) {
536
debugs(86, 3, "ESIIncludeSubRequestDone: ALT FAILED, Include FAILED - SRC already failed");
537
/* src already failed */
544
fatal ("ESIIncludeSubRequestDone: non-owned stream found!\n");
547
if (flags.finished || flags.failed) {
548
/* Kick ESI Processor */
549
debugs (86, 5, "ESIInclude " << this <<
550
" SubRequest " << stream.getRaw() <<
551
" completed, kicking processor , status " <<
552
(flags.finished ? "OK" : "FAILED"));
553
/* There is a race condition - and we have no reproducible test case -
554
* during a subrequest the parent will get set to NULL, which is not
555
* meant to be possible. Rather than killing squid, we let it leak
556
* memory but complain in the log.
558
* Someone wanting to debug this could well start by running squid with
559
* a hardware breakpoint set to this location.
560
* Its probably due to parent being set to null - by a call to
561
* 'this.finish' while the subrequest is still not completed.
563
if (parent.getRaw() == NULL) {
564
debugs (86, 0, "ESIInclude::subRequestDone: Sub request completed "
565
"after finish() called and parent unlinked. Unable to "
566
"continue handling the request, and may be memory leaking. "
567
"See http://www.squid-cache.org/bugs/show_bug.cgi?id=951 - we "
568
"are looking for a reproducible test case. This will require "
569
"an ESI template with includes, probably with alt-options, "
570
"and we're likely to need traffic dumps to allow us to "
571
"reconstruct the exact tcp handling sequences to trigger this "
572
"rather elusive bug.");
575
assert (parent.getRaw());
579
parent->provideData (srccontent.getRaw() ? srccontent:altcontent,this);
581
if (srccontent.getRaw())
585
} else if (flags.onerrorcontinue) {
586
/* render nothing but inform of completion */
590
parent->provideData (new ESISegment, this);
594
parent->fail(this, "esi:include could not be completed.");
598
#endif /* USE_SQUID_ESI == 1 */