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