]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ESIInclude.cc
Updates for running on squid-cache.org
[thirdparty/squid.git] / src / ESIInclude.cc
CommitLineData
924f73bc 1
2/*
9bd63d11 3 * $Id: ESIInclude.cc,v 1.17 2008/02/12 23:52:28 rousskov Exp $
924f73bc 4 *
5 * DEBUG: section 86 ESI processing
6 * AUTHOR: Robert Collins
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
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.
19 *
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.
24 *
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.
29 *
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.
33 *
34 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
35 */
36
37#include "squid.h"
454e8283 38
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.
41 */
42#if (USE_SQUID_ESI == 1)
43
924f73bc 44#include "ESIInclude.h"
45#include "ESIVarState.h"
46#include "client_side_request.h"
47#include "HttpReply.h"
48
49CBDATA_CLASS_INIT (ESIStreamContext);
50
924f73bc 51/* other */
52static CSCB esiBufferRecipient;
53static CSD esiBufferDetach;
54/* esiStreamContext */
55static ESIStreamContext *ESIStreamContextNew (ESIIncludePtr);
56
57/* ESI TO CONSIDER:
58 * 1. retry failed upstream requests
59 */
60
61/* Detach from a buffering stream
62 */
63void
59a1efb2 64esiBufferDetach (clientStreamNode *node, ClientHttpRequest *http)
924f73bc 65{
66 /* Detach ourselves */
67 clientStreamDetach (node, http);
68}
69
24cf4917
AJ
70/**
71 * Write a chunk of data to a client 'socket'.
72 * If the reply is present, send the reply headers down the wire too.
73 *
74 * Pre-condition:
924f73bc 75 * The request is an internal ESI subrequest.
76 * data context is not NULL
77 * There are no more entries in the stream chain.
24cf4917
AJ
78 * The caller is responsible for creation and deletion of the Reply headers.
79 *
80 \note
81 * Bug 975, bug 1566 : delete rep; 2006/09/02: TS, #975
82 *
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
924f73bc 86 */
87void
2324cda2 88esiBufferRecipient (clientStreamNode *node, ClientHttpRequest *http, HttpReply *rep, StoreIOBuffer receivedData)
924f73bc 89{
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
97 * assert for now. */
98 assert (cbdataReferenceValid (node));
99 assert (node->node.next == NULL);
9bd63d11 100 assert (http->getConn() == NULL);
924f73bc 101
102 ESIStreamContext::Pointer esiStream = dynamic_cast<ESIStreamContext *>(node->data.getRaw());
103 assert (esiStream.getRaw() != NULL);
104 /* If segments become more flexible, ignore thisNode */
2324cda2 105 assert (receivedData.length <= sizeof(esiStream->localbuffer->buf));
924f73bc 106 assert (!esiStream->finished);
107
24cf4917 108 debugs (86,5, HERE << "rep " << rep << " body " << receivedData.data << " len " << receivedData.length);
2324cda2 109 assert (node->readBuffer.offset == receivedData.offset || receivedData.length == 0);
924f73bc 110
111 /* trivial case */
112
113 if (http->out.offset != 0) {
114 assert(rep == NULL);
115 } else {
116 if (rep) {
117 if (rep->sline.status != HTTP_OK) {
924f73bc 118 rep = NULL;
119 esiStream->include->fail (esiStream);
120 esiStream->finished = 1;
121 httpRequestFree (http);
122 return;
123 }
124
125#if HEADERS_LOG
126 /* should be done in the store rather than every recipient? */
127 headersLog(0, 0, http->request->method, rep);
128
129#endif
924f73bc 130 rep = NULL;
131 }
132 }
133
2324cda2 134 if (receivedData.data && receivedData.length) {
135 http->out.offset += receivedData.length;
924f73bc 136
2324cda2 137 if (receivedData.data >= esiStream->localbuffer->buf &&
138 receivedData.data < &esiStream->localbuffer->buf[sizeof(esiStream->localbuffer->buf)]) {
924f73bc 139 /* original static buffer */
140
2324cda2 141 if (receivedData.data != esiStream->localbuffer->buf) {
924f73bc 142 /* But not the start of it */
2324cda2 143 xmemmove (esiStream->localbuffer->buf, receivedData.data, receivedData.length);
924f73bc 144 }
145
2324cda2 146 esiStream->localbuffer->len = receivedData.length;
924f73bc 147 } else {
148 assert (esiStream->buffer.getRaw() != NULL);
2324cda2 149 esiStream->buffer->len = receivedData.length;
924f73bc 150 }
151 }
152
153 /* EOF / Read error / aborted entry */
2324cda2 154 if (rep == NULL && receivedData.data == NULL && receivedData.length == 0) {
924f73bc 155 /* TODO: get stream status to test the entry for aborts */
24cf4917 156 debugs(86, 5, HERE << "Finished reading upstream data in subrequest");
924f73bc 157 esiStream->include->subRequestDone (esiStream, true);
158 esiStream->finished = 1;
159 httpRequestFree (http);
160 return;
161 }
162
163
164 /* after the write to the user occurs, (ie here, or in a callback)
165 * we call */
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 */
169 node->data = NULL;
170 esiStream->finished = 1;
171 esiStream->include->fail (esiStream);
172 return;
173 };
174
175 switch (clientStreamStatus (node, http)) {
176
177 case STREAM_UNPLANNED_COMPLETE: /* fallthru ok */
178
179 case STREAM_COMPLETE: /* ok */
bf8fe701 180 debugs(86, 3, "ESI subrequest finished OK");
924f73bc 181 esiStream->include->subRequestDone (esiStream, true);
182 esiStream->finished = 1;
183 httpRequestFree (http);
184 return;
185
186 case STREAM_FAILED:
bf8fe701 187 debugs(86, 1, "ESI subrequest failed transfer");
924f73bc 188 esiStream->include->fail (esiStream);
189 esiStream->finished = 1;
190 httpRequestFree (http);
191 return;
192
193 case STREAM_NONE: {
194 StoreIOBuffer tempBuffer;
195
196 if (!esiStream->buffer.getRaw()) {
197 esiStream->buffer = esiStream->localbuffer;
198 }
199
200 esiStream->buffer = esiStream->buffer->tail();
201
202 if (esiStream->buffer->len) {
203 esiStream->buffer->next = new ESISegment;
204 esiStream->buffer = esiStream->buffer->next;
205 }
206
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' */
24cf4917
AJ
211 clientStreamRead (node, http, tempBuffer);
212 debugs(86, 5, HERE << "Requested more data for ESI subrequest");
924f73bc 213 }
214
215 break;
216
217 default:
218 fatal ("Hit unreachable code in esiBufferRecipient\n");
219 }
220
221}
222
223/* esiStream functions */
224ESIStreamContext::~ESIStreamContext()
225{
226 assert (this);
227 freeResources();
228}
229
230void
231ESIStreamContext::freeResources()
232{
bf8fe701 233 debugs(86, 5, "Freeing stream context resources.");
924f73bc 234 buffer = NULL;
235 localbuffer = NULL;
236 include = NULL;
237}
238
239void *
240ESIStreamContext::operator new(size_t byteCount)
241{
242 assert (byteCount == sizeof (ESIStreamContext));
243 CBDATA_INIT_TYPE(ESIStreamContext);
244 ESIStreamContext *result = cbdataAlloc(ESIStreamContext);
924f73bc 245 return result;
246}
247
248void
249ESIStreamContext::operator delete (void *address)
250{
251 ESIStreamContext *t = static_cast<ESIStreamContext *>(address);
252 cbdataFree(t);
924f73bc 253}
254
924f73bc 255ESIStreamContext *
256ESIStreamContextNew (ESIIncludePtr include)
257{
258 ESIStreamContext *rv = new ESIStreamContext;
259 rv->include = include;
260 return rv;
261}
262
263
264
265/* ESIInclude */
266ESIInclude::~ESIInclude()
267{
bf8fe701 268 debugs(86, 5, "ESIInclude::Free " << this);
924f73bc 269 ESISegmentFreeList (srccontent);
270 ESISegmentFreeList (altcontent);
271 cbdataReferenceDone (varState);
272 safe_free (srcurl);
273 safe_free (alturl);
274}
275
276void
277ESIInclude::finish()
278{
279 parent = NULL;
280}
281
924f73bc 282ESIElement::Pointer
283ESIInclude::makeCacheable() const
284{
285 return new ESIInclude (*this);
286}
287
288ESIElement::Pointer
289ESIInclude::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
290{
291 ESIInclude *resultI = new ESIInclude (*this);
292 ESIElement::Pointer result = resultI;
293 resultI->parent = newParent;
294 resultI->varState = cbdataReference (&newVarState);
295
296 if (resultI->srcurl)
297 resultI->src = ESIStreamContextNew (resultI);
298
299 if (resultI->alturl)
300 resultI->alt = ESIStreamContextNew (resultI);
301
302 return result;
303}
304
305ESIInclude::ESIInclude(ESIInclude const &old) : parent (NULL), started (false), sent (false)
306{
307 varState = NULL;
308 flags.onerrorcontinue = old.flags.onerrorcontinue;
309
310 if (old.srcurl)
311 srcurl = xstrdup (old.srcurl);
312
313 if (old.alturl)
314 alturl = xstrdup (old.alturl);
315}
316
317void
318ESIInclude::prepareRequestHeaders(HttpHeader &tempheaders, ESIVarState *vars)
319{
924f73bc 320 tempheaders.update (&vars->header(), NULL);
3b6b6092 321 tempheaders.removeHopByHopEntries();
924f73bc 322}
323
324
325void
326ESIInclude::Start (ESIStreamContext::Pointer stream, char const *url, ESIVarState *vars)
327{
924f73bc 328 if (!stream.getRaw())
329 return;
330
818c6c9e 331 HttpHeader tempheaders(hoRequest);
332
924f73bc 333 prepareRequestHeaders(tempheaders, vars);
334
335 /* Ensure variable state is clean */
336 vars->feedData(url, strlen (url));
337
338 /* tempUrl is eaten by the request */
339 char const *tempUrl = vars->extractChar ();
340
bf8fe701 341 debugs(86, 5, "ESIIncludeStart: Starting subrequest with url '" << tempUrl <<
342 "'");
924f73bc 343
344 if (clientBeginRequest(METHOD_GET, tempUrl, esiBufferRecipient, esiBufferDetach, stream.getRaw(), &tempheaders, stream->localbuffer->buf, HTTP_REQBUF_SZ)) {
bf8fe701 345 debugs(86, 0, "starting new ESI subrequest failed");
924f73bc 346 }
347
519e0948 348 tempheaders.clean();
924f73bc 349}
350
351ESIInclude::ESIInclude (esiTreeParentPtr aParent, int attrcount, char const **attr, ESIContext *aContext) : parent (aParent), started (false), sent (false)
352{
353 int i;
354 assert (aContext);
355
356 for (i = 0; i < attrcount && attr[i]; i += 2) {
357 if (!strcmp(attr[i],"src")) {
358 /* Start a request for thisNode url */
bf8fe701 359 debugs(86, 5, "ESIIncludeNew: Requesting source '" << attr[i+1] << "'");
360
924f73bc 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
370 */
bf8fe701 371 debugs(86, 5, "ESIIncludeNew: Requesting alternate '" << attr[i+1] << "'");
372
924f73bc 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;
380 } else {
381 /* ignore mistyped attributes */
bf8fe701 382 debugs(86, 1, "invalid value for onerror='" << attr[i+1] << "'");
924f73bc 383 }
384 } else {
385 /* ignore mistyped attributes. TODO:? error on these for user feedback - config parameter needed
386 */
387 }
388 }
389
390 varState = cbdataReference(aContext->varState);
391}
392
393void
394ESIInclude::start()
395{
396 /* prevent freeing ourselves */
397 ESIIncludePtr foo(this);
398
399 if (started)
400 return;
401
402 started = true;
403
404 if (src.getRaw()) {
405 Start (src, srcurl, varState);
406 Start (alt, alturl, varState);
407 } else {
408 alt = NULL;
409
bf8fe701 410 debugs(86, 1, "ESIIncludeNew: esi:include with no src attributes");
924f73bc 411
412 flags.failed = 1;
413 }
414}
415
416void
417ESIInclude::render(ESISegment::Pointer output)
418{
419 if (sent)
420 return;
421
422 ESISegment::Pointer myout;
423
bf8fe701 424 debugs(86, 5, "ESIIncludeRender: Rendering include " << this);
924f73bc 425
426 assert (flags.finished || (flags.failed && flags.onerrorcontinue));
427
428 if (flags.failed && flags.onerrorcontinue) {
429 return;
430 }
431
432 /* Render the content */
433 if (srccontent.getRaw()) {
434 myout = srccontent;
435 srccontent = NULL;
436 } else if (altcontent.getRaw()) {
437 myout = altcontent;
438 altcontent = NULL;
439 } else
440 fatal ("ESIIncludeRender called with no content, and no failure!\n");
441
442 assert (output->next == NULL);
443
444 output->next = myout;
445
446 sent = true;
447}
448
449esiProcessResult_t
450ESIInclude::process (int dovars)
451{
452 /* Prevent refcount race leading to free */
453 Pointer me (this);
454 start();
bf8fe701 455 debugs(86, 5, "ESIIncludeRender: Processing include " << this);
924f73bc 456
457 if (flags.failed) {
458 if (flags.onerrorcontinue)
459 return ESI_PROCESS_COMPLETE;
460 else
461 return ESI_PROCESS_FAILED;
462 }
463
464 if (!flags.finished) {
465 if (flags.onerrorcontinue)
466 return ESI_PROCESS_PENDING_WONTFAIL;
467 else
468 return ESI_PROCESS_PENDING_MAYFAIL;
469 }
470
471 return ESI_PROCESS_COMPLETE;
472}
473
474void
475ESIInclude::fail (ESIStreamContext::Pointer stream)
476{
477 subRequestDone (stream, false);
478}
479
480bool
481ESIInclude::dataNeeded() const
482{
483 return !(flags.finished || flags.failed);
484}
485
486void
487ESIInclude::subRequestDone (ESIStreamContext::Pointer stream, bool success)
488{
489 assert (this);
490
491 if (!dataNeeded())
492 return;
493
494 if (stream == src) {
bf8fe701 495 debugs(86, 3, "ESIInclude::subRequestDone: " << srcurl);
924f73bc 496
497 if (success) {
498 /* copy the lead segment */
bf8fe701 499 debugs(86, 3, "ESIIncludeSubRequestDone: Src OK - include PASSED.");
924f73bc 500 assert (!srccontent.getRaw());
501 ESISegment::ListTransfer (stream->localbuffer, srccontent);
502 /* we're done! */
503 flags.finished = 1;
504 } else {
505 /* Fail if there is no alt being retrieved */
bf8fe701 506 debugs(86, 3, "ESIIncludeSubRequestDone: Src FAILED");
924f73bc 507
508 if (!(alt.getRaw() || altcontent.getRaw())) {
bf8fe701 509 debugs(86, 3, "ESIIncludeSubRequestDone: Include FAILED - No ALT");
924f73bc 510 flags.failed = 1;
511 } else if (altcontent.getRaw()) {
bf8fe701 512 debugs(86, 3, "ESIIncludeSubRequestDone: Include PASSED - ALT already Complete");
924f73bc 513 /* ALT was already retrieved, we are done */
514 flags.finished = 1;
515 }
516 }
517
518 src = NULL;
519 } else if (stream == alt) {
bf8fe701 520 debugs(86, 3, "ESIInclude::subRequestDone: " << alturl);
924f73bc 521
522 if (success) {
bf8fe701 523 debugs(86, 3, "ESIIncludeSubRequestDone: ALT OK.");
924f73bc 524 /* copy the lead segment */
525 assert (!altcontent.getRaw());
526 ESISegment::ListTransfer (stream->localbuffer, altcontent);
527 /* we're done! */
528
529 if (!(src.getRaw() || srccontent.getRaw())) {
530 /* src already failed, kick ESI processor */
bf8fe701 531 debugs(86, 3, "ESIIncludeSubRequestDone: Include PASSED - SRC already failed.");
924f73bc 532 flags.finished = 1;
533 }
534 } else {
535 if (!(src.getRaw() || srccontent.getRaw())) {
bf8fe701 536 debugs(86, 3, "ESIIncludeSubRequestDone: ALT FAILED, Include FAILED - SRC already failed");
924f73bc 537 /* src already failed */
538 flags.failed = 1;
539 }
540 }
541
542 alt = NULL;
543 } else {
544 fatal ("ESIIncludeSubRequestDone: non-owned stream found!\n");
545 }
546
547 if (flags.finished || flags.failed) {
548 /* Kick ESI Processor */
2a2d937f 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.
557 *
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.
562 */
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.");
573 return;
574 }
924f73bc 575 assert (parent.getRaw());
576
577 if (!flags.failed) {
578 sent = true;
579 parent->provideData (srccontent.getRaw() ? srccontent:altcontent,this);
580
581 if (srccontent.getRaw())
582 srccontent = NULL;
583 else
584 altcontent = NULL;
585 } else if (flags.onerrorcontinue) {
586 /* render nothing but inform of completion */
587
588 if (!sent) {
589 sent = true;
590 parent->provideData (new ESISegment, this);
591 } else
592 assert (0);
593 } else
594 parent->fail(this, "esi:include could not be completed.");
595 }
596}
597
454e8283 598#endif /* USE_SQUID_ESI == 1 */