]>
Commit | Line | Data |
---|---|---|
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 | ||
43 | CBDATA_CLASS_INIT (ESIStreamContext); | |
44 | ||
45 | MemPool *ESIInclude::Pool = NULL; | |
46 | ||
47 | /* other */ | |
48 | static CSCB esiBufferRecipient; | |
49 | static CSD esiBufferDetach; | |
50 | /* esiStreamContext */ | |
51 | static ESIStreamContext *ESIStreamContextNew (ESIIncludePtr); | |
52 | ||
53 | /* ESI TO CONSIDER: | |
54 | * 1. retry failed upstream requests | |
55 | */ | |
56 | ||
57 | /* Detach from a buffering stream | |
58 | */ | |
59 | void | |
60 | esiBufferDetach (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 | */ | |
75 | void | |
76 | esiBufferRecipient (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 */ | |
217 | ESIStreamContext::~ESIStreamContext() | |
218 | { | |
219 | assert (this); | |
220 | freeResources(); | |
221 | } | |
222 | ||
223 | void | |
224 | ESIStreamContext::freeResources() | |
225 | { | |
226 | debug (86,5)("Freeing stream context resources.\n"); | |
227 | buffer = NULL; | |
228 | localbuffer = NULL; | |
229 | include = NULL; | |
230 | } | |
231 | ||
232 | void * | |
233 | ESIStreamContext::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 | ||
245 | void | |
246 | ESIStreamContext::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 | ||
254 | void | |
255 | ESIStreamContext::deleteSelf() const | |
256 | { | |
257 | delete this; | |
258 | } | |
259 | ||
260 | ESIStreamContext * | |
261 | ESIStreamContextNew (ESIIncludePtr include) | |
262 | { | |
263 | ESIStreamContext *rv = new ESIStreamContext; | |
264 | rv->include = include; | |
265 | return rv; | |
266 | } | |
267 | ||
268 | ||
269 | ||
270 | /* ESIInclude */ | |
271 | ESIInclude::~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 | ||
281 | void | |
282 | ESIInclude::finish() | |
283 | { | |
284 | parent = NULL; | |
285 | } | |
286 | ||
287 | void * | |
288 | ESIInclude::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 | ||
298 | void | |
299 | ESIInclude::operator delete (void *address) | |
300 | { | |
301 | memPoolFree (Pool, address); | |
302 | } | |
303 | ||
304 | void | |
305 | ESIInclude::deleteSelf() const | |
306 | { | |
307 | delete this; | |
308 | } | |
309 | ||
310 | ESIElement::Pointer | |
311 | ESIInclude::makeCacheable() const | |
312 | { | |
313 | return new ESIInclude (*this); | |
314 | } | |
315 | ||
316 | ESIElement::Pointer | |
317 | ESIInclude::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 | ||
333 | ESIInclude::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 | ||
345 | void | |
346 | ESIInclude::prepareRequestHeaders(HttpHeader &tempheaders, ESIVarState *vars) | |
347 | { | |
348 | httpHeaderInit (&tempheaders, hoRequest); | |
349 | ||
350 | tempheaders.update (&vars->header(), NULL); | |
351 | tempheaders.removeConnectionHeaderEntries(); | |
352 | } | |
353 | ||
354 | ||
355 | void | |
356 | ESIInclude::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 | ||
380 | ESIInclude::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 | ||
420 | void | |
421 | ESIInclude::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 | ||
443 | void | |
444 | ESIInclude::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 | ||
476 | esiProcessResult_t | |
477 | ESIInclude::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 | ||
501 | void | |
502 | ESIInclude::fail (ESIStreamContext::Pointer stream) | |
503 | { | |
504 | subRequestDone (stream, false); | |
505 | } | |
506 | ||
507 | bool | |
508 | ESIInclude::dataNeeded() const | |
509 | { | |
510 | return !(flags.finished || flags.failed); | |
511 | } | |
512 | ||
513 | void | |
514 | ESIInclude::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 |