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