3 * $Id: client_side_reply.cc,v 1.35 2003/02/05 01:27:11 robertc Exp $
5 * DEBUG: section 88 Client-side Reply Routines
6 * AUTHOR: Robert Collins (Originally Duane Wessels in client_side.c)
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.
37 #include "StoreClient.h"
39 #include "HttpReply.h"
40 #include "HttpRequest.h"
42 #include "clientStream.h"
43 #include "authenticate.h"
44 #include "MemObject.h"
45 #include "client_side_request.h"
46 #include "ACLChecklist.h"
48 static STCB clientHandleIMSReply
;
49 static STCB clientSendMoreData
;
50 class clientReplyContext
: public StoreClient
{
52 void *operator new (size_t byteCount
);
53 void operator delete (void *address
);
55 void saveState(clientHttpRequest
*);
56 void restoreState(clientHttpRequest
*);
58 void purgeRequestFindObjectToPurge();
59 void purgeDoMissPurge();
60 void purgeFoundGet(StoreEntry
*newEntry
);
61 void purgeFoundHead(StoreEntry
*newEntry
);
62 void purgeFoundObject(StoreEntry
*entry
);
63 void sendClientUpstreamResponse();
64 void purgeDoPurgeGet(StoreEntry
*entry
);
65 void purgeDoPurgeHead(StoreEntry
*entry
);
67 void identifyStoreObject();
68 void identifyFoundObject(StoreEntry
*entry
);
69 int storeOKTransferDone() const;
70 int storeNotOKTransferDone() const;
72 http_status purgeStatus
;
74 /* state variable - replace with class to handle storeentries at some point */
76 virtual void created (StoreEntry
*newEntry
);
78 clientHttpRequest
*http
;
80 store_client
*sc
; /* The store_client we're using */
81 store_client
*old_sc
; /* ... for entry to be validated */
82 StoreIOBuffer tempBuffer
; /* For use in validating requests via IMS */
83 int old_reqsize
; /* ... again, for the buffer */
86 char tempbuf
[HTTP_REQBUF_SZ
]; /* a temporary buffer if we need working storage */
88 const char *lookup_type
; /* temporary hack: storeGet() result: HIT/MISS/NONE */
91 int storelogiccomplete
:1;
92 int complete
:1; /* we have read all we can from upstream */
95 clientStreamNode
*ourNode
; /* This will go away if/when this file gets refactored some more */
97 friend void clientSendMoreData(void *data
, StoreIOBuffer result
);
98 friend void clientHandleIMSReply(void *data
, StoreIOBuffer result
);
99 static STCB clientSendMoreData
;
100 clientStreamNode
*getNextNode() const;
101 void sendMoreData (StoreIOBuffer result
);
103 bool errorInStream(StoreIOBuffer
const &result
, size_t const &sizeToProcess
)const ;
104 void sendStreamError(StoreIOBuffer
const &result
);
105 void pushStreamData(StoreIOBuffer
const &result
, char *source
);
106 void waitForMoreData (StoreIOBuffer
const &result
);
107 clientStreamNode
* next() const;
108 void startSendProcess();
109 StoreIOBuffer holdingBuffer
;
110 HttpReply
*holdingReply
;
111 void processReplyAccess();
112 static PF ProcessReply
;
113 void processReply(bool accessAllowed
);
116 CBDATA_TYPE(clientReplyContext
);
118 /* Local functions */
119 static int clientGotNotEnough(clientHttpRequest
const *);
120 static int clientReplyBodyTooLarge(HttpReply
const *, ssize_t
);
121 static int clientOnlyIfCached(clientHttpRequest
* http
);
122 static void clientProcessExpired(clientReplyContext
*);
123 static void clientProcessMiss(clientReplyContext
*);
124 static STCB clientCacheHit
;
125 static void clientProcessOnlyIfCachedMiss(clientReplyContext
*);
126 static int clientGetsOldEntry(StoreEntry
*, StoreEntry
* old
,
127 request_t
* request
);
129 static int modifiedSince(StoreEntry
*, request_t
*);
130 static void clientTraceReply(clientStreamNode
*, clientReplyContext
*);
131 static StoreEntry
*clientCreateStoreEntry(clientReplyContext
*, method_t
,
134 static void clientRemoveStoreReference(clientReplyContext
*, store_client
**,
136 extern "C" CSS clientReplyStatus
;
137 extern ErrorState
*clientBuildError(err_type
, http_status
, char const *,
138 struct in_addr
*, request_t
*);
140 static void startError(clientReplyContext
* context
, clientHttpRequest
* http
, ErrorState
* err
);
141 static void triggerInitialStoreReadWithClientParameters(clientReplyContext
* context
, clientHttpRequest
* http
);
142 static int clientCheckTransferDone(clientReplyContext
* context
);
143 static void clientObeyConnectionHeader(clientHttpRequest
* http
, HttpReply
* rep
);
145 /* The clientReply clean interface */
147 static FREE clientReplyFree
;
150 clientReplyFree(void *data
)
152 clientReplyContext
*thisClient
= (clientReplyContext
*)data
;
153 clientRemoveStoreReference(thisClient
, &thisClient
->sc
, &thisClient
->http
->entry
);
154 /* old_entry might still be set if we didn't yet get the reply
155 * code in clientHandleIMSReply() */
156 clientRemoveStoreReference(thisClient
, &thisClient
->old_sc
, &thisClient
->http
->old_entry
);
157 safe_free(thisClient
->tempBuffer
.data
);
158 cbdataReferenceDone(thisClient
->http
);
162 clientReplyNewContext(clientHttpRequest
* clientContext
)
164 clientReplyContext
*context
= new clientReplyContext
;
165 context
->http
= cbdataReference(clientContext
);
169 /* create an error in the store awaiting the client side to read it. */
171 clientSetReplyToError(void *data
,
172 err_type err
, http_status status
, method_t method
, char const *uri
,
173 struct in_addr
*addr
, request_t
* failedrequest
, char *unparsedrequest
,
174 auth_user_request_t
* auth_user_request
)
176 clientReplyContext
*context
= (clientReplyContext
*)data
;
177 ErrorState
*errstate
=
178 clientBuildError(err
, status
, uri
, addr
, failedrequest
);
180 errstate
->request_hdrs
= xstrdup(unparsedrequest
);
182 if (status
== HTTP_NOT_IMPLEMENTED
&& context
->http
->request
)
183 /* prevent confusion over whether we default to persistent or not */
184 context
->http
->request
->flags
.proxy_keepalive
= 0;
185 context
->http
->al
.http
.code
= errstate
->httpStatus
;
187 context
->http
->entry
=
188 clientCreateStoreEntry(context
, method
, request_flags());
189 if (auth_user_request
) {
190 errstate
->auth_user_request
= auth_user_request
;
191 authenticateAuthUserRequestLock(errstate
->auth_user_request
);
193 assert(errstate
->callback_data
== NULL
);
194 errorAppendEntry(context
->http
->entry
, errstate
);
195 /* Now the caller reads to get this */
199 clientRemoveStoreReference(clientReplyContext
* context
, store_client
** scp
,
203 store_client
*sc
= *scp
;
204 if ((e
= *ep
) != NULL
) {
206 storeUnregister(sc
, e
, context
);
208 storeUnlockObject(e
);
213 clientReplyContext::operator new (size_t byteCount
)
215 /* derived classes with different sizes must implement their own new */
216 assert (byteCount
== sizeof (clientReplyContext
));
217 CBDATA_INIT_TYPE_FREECB(clientReplyContext
, clientReplyFree
);
218 return cbdataAlloc(clientReplyContext
);
222 clientReplyContext::operator delete (void *address
)
224 cbdataFree ((clientReplyContext
*)address
);
229 clientReplyContext::saveState(clientHttpRequest
* http
)
231 assert(old_sc
== NULL
);
232 debug(88, 1)("clientReplyContext::saveState: saving store context\n");
233 http
->old_entry
= http
->entry
;
235 old_reqsize
= reqsize
;
236 tempBuffer
.offset
= reqofs
;
237 /* Prevent accessing the now saved entries */
245 clientReplyContext::restoreState(clientHttpRequest
* http
)
247 assert(old_sc
!= NULL
);
248 debug(88, 1)("clientReplyContext::restoreState: Restoring store context\n");
249 http
->entry
= http
->old_entry
;
251 reqsize
= old_reqsize
;
252 reqofs
= tempBuffer
.offset
;
253 /* Prevent accessed the old saved entries */
254 http
->old_entry
= NULL
;
257 tempBuffer
.offset
= 0;
261 startError(clientReplyContext
* context
, clientHttpRequest
* http
, ErrorState
* err
)
263 http
->entry
= clientCreateStoreEntry(context
, http
->request
->method
, request_flags());
264 triggerInitialStoreReadWithClientParameters(context
, http
);
265 errorAppendEntry(http
->entry
, err
);
269 clientReplyContext::getNextNode() const
271 return (clientStreamNode
*)ourNode
->node
.next
->data
;
274 /* This function is wrong - the client parameters don't include the
278 triggerInitialStoreReadWithClientParameters(clientReplyContext
* context
, clientHttpRequest
* http
)
280 clientStreamNode
*next
= (clientStreamNode
*)http
->client_stream
.head
->next
->data
;
281 StoreIOBuffer tempBuffer
;
282 /* collapse this to one object if we never tickle the assert */
283 assert(context
->http
== http
);
284 /* when confident, 0 becomes context->reqofs, and then this factors into
287 assert(context
->reqofs
== 0);
288 tempBuffer
.offset
= 0;
289 tempBuffer
.length
= next
->readBuffer
.length
;
290 tempBuffer
.data
= next
->readBuffer
.data
;
291 storeClientCopy(context
->sc
, http
->entry
, tempBuffer
, clientSendMoreData
, context
);
294 /* there is an expired entry in the store.
295 * setup a temporary buffer area and perform an IMS to the origin
298 clientProcessExpired(clientReplyContext
* context
)
300 clientHttpRequest
*http
= context
->http
;
301 char *url
= http
->uri
;
302 StoreEntry
*entry
= NULL
;
303 debug(88, 3)("clientProcessExpired: '%s'", http
->uri
);
304 assert(http
->entry
->lastmod
>= 0);
306 * check if we are allowed to contact other servers
307 * @?@: Instead of a 504 (Gateway Timeout) reply, we may want to return
308 * a stale entry *if* it matches client requirements
310 if (clientOnlyIfCached(http
)) {
311 clientProcessOnlyIfCachedMiss(context
);
314 http
->request
->flags
.refresh
= 1;
315 #if STORE_CLIENT_LIST_DEBUG
316 /* Prevent a race with the store client memory free routines
318 assert(storeClientIsThisAClient(context
->sc
, context
));
320 /* Prepare to make a new temporary request */
321 context
->saveState(http
);
322 entry
= storeCreateEntry(url
,
323 http
->log_uri
, http
->request
->flags
, http
->request
->method
);
324 /* NOTE, don't call storeLockObject(), storeCreateEntry() does it */
325 context
->sc
= storeClientListAdd(entry
, context
);
327 /* delay_id is already set on original store client */
328 delaySetStoreClient(context
->sc
, delayClient(http
));
330 http
->request
->lastmod
= http
->old_entry
->lastmod
;
331 debug(88, 5)("clientProcessExpired : lastmod %ld",
332 (long int) entry
->lastmod
);
334 assert(http
->out
.offset
== 0);
335 fwdStart(http
->conn
? http
->conn
->fd
: -1, http
->entry
, http
->request
);
336 /* Register with storage manager to receive updates when data comes in. */
337 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
))
338 debug(88, 0) ("clientProcessExpired: Found ENTRY_ABORTED object");
340 StoreIOBuffer tempBuffer
;
341 /* start counting the length from 0 */
342 tempBuffer
.offset
= 0;
343 tempBuffer
.length
= HTTP_REQBUF_SZ
;
344 tempBuffer
.data
= context
->tempbuf
;
345 storeClientCopy(context
->sc
, entry
,
346 tempBuffer
, clientHandleIMSReply
, context
);
351 modifiedSince(StoreEntry
* entry
, request_t
* request
)
354 time_t mod_time
= entry
->lastmod
;
355 debug(88, 3) ("modifiedSince: '%s'\n", storeUrl(entry
));
357 mod_time
= entry
->timestamp
;
358 debug(88, 3) ("modifiedSince: mod_time = %ld\n", (long int) mod_time
);
361 /* Find size of the object */
362 object_length
= entry
->getReply()->content_length
;
363 if (object_length
< 0)
364 object_length
= contentLen(entry
);
365 if (mod_time
> request
->ims
) {
366 debug(88, 3) ("--> YES: entry newer than client\n");
368 } else if (mod_time
< request
->ims
) {
369 debug(88, 3) ("--> NO: entry older than client\n");
371 } else if (request
->imslen
< 0) {
372 debug(88, 3) ("--> NO: same LMT, no client length\n");
374 } else if (request
->imslen
== object_length
) {
375 debug(88, 3) ("--> NO: same LMT, same length\n");
378 debug(88, 3) ("--> YES: same LMT, different length\n");
384 clientGetsOldEntry(StoreEntry
* new_entry
, StoreEntry
* old_entry
,
387 const http_status status
= new_entry
->getReply()->sline
.status
;
389 debug(88, 5) ("clientGetsOldEntry: YES, broken HTTP reply\n");
392 /* If the reply is a failure then send the old object as a last
394 if (status
>= 500 && status
< 600) {
395 debug(88, 3) ("clientGetsOldEntry: YES, failure reply=%d\n", status
);
398 /* If the reply is anything but "Not Modified" then
399 * we must forward it to the client */
400 if (HTTP_NOT_MODIFIED
!= status
) {
401 debug(88, 5) ("clientGetsOldEntry: NO, reply=%d\n", status
);
404 /* If the client did not send IMS in the request, then it
405 * must get the old object, not this "Not Modified" reply
406 * REGARDLESS of validation */
407 if (!request
->flags
.ims
) {
408 debug(88, 5) ("clientGetsOldEntry: YES, no client IMS\n");
411 /* If key metadata in the reply are not consistent with the
412 * old entry, we must use the new reply.
413 * Note: this means that the server is sending garbage replies
414 * in that it has sent an IMS that is incompatible with our request!?
416 /* This is a duplicate call through the HandleIMS code path.
417 * Can we guarantee we don't need it elsewhere?
419 if (!httpReplyValidatorsMatch(new_entry
->getReply(),
420 old_entry
->getReply())) {
421 debug(88, 5) ("clientGetsOldEntry: NO, Old object has been invalidated"
425 /* If the client IMS time is prior to the entry LASTMOD time we
426 * need to send the old object */
427 if (modifiedSince(old_entry
, request
)) {
428 debug(88, 5) ("clientGetsOldEntry: YES, modified since %ld\n",
429 (long int) request
->ims
);
432 debug(88, 5) ("clientGetsOldEntry: NO, new one is fine\n");
437 clientReplyContext::sendClientUpstreamResponse()
439 StoreIOBuffer tempresult
;
440 http
->logType
= LOG_TCP_REFRESH_MISS
;
441 clientRemoveStoreReference(this, &old_sc
, &http
->old_entry
);
442 /* here the data to send is the data we just recieved */
443 tempBuffer
.offset
= 0;
445 /* clientSendMoreData tracks the offset as well.
446 * Force it back to zero */
448 assert(!EBIT_TEST(http
->entry
->flags
, ENTRY_ABORTED
));
449 /* TODO: provide SendMoreData with the ready parsed reply */
450 tempresult
.length
= reqsize
;
451 tempresult
.data
= tempbuf
;
452 clientSendMoreData(this, tempresult
);
456 clientHandleIMSReply(void *data
, StoreIOBuffer result
)
458 clientReplyContext
*context
= (clientReplyContext
*)data
;
459 clientHttpRequest
*http
= context
->http
;
460 StoreEntry
*entry
= http
->entry
;
461 const char *url
= storeUrl(entry
);
462 int unlink_request
= 0;
463 StoreEntry
*oldentry
;
465 debug(88, 3) ("clientHandleIMSReply: %s, %lu bytes\n", url
,
466 (long unsigned) result
.length
);
470 if (result
.flags
.error
&& !EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
473 /* update size of the request */
474 context
->reqsize
= result
.length
+ context
->reqofs
;
475 status
= entry
->getReply()->sline
.status
;
476 if (EBIT_TEST(entry
->flags
, ENTRY_ABORTED
)) {
477 debug(88, 3) ("clientHandleIMSReply: ABORTED '%s'\n", url
);
478 /* We have an existing entry, but failed to validate it */
479 /* Its okay to send the old one anyway */
480 http
->logType
= LOG_TCP_REFRESH_FAIL_HIT
;
481 clientRemoveStoreReference(context
, &context
->sc
, &entry
);
482 /* Get the old request back */
483 context
->restoreState(http
);
487 if (STORE_PENDING
== entry
->store_status
&& 0 == status
) {
488 /* more headers needed to decide */
489 debug(88, 3) ("clientHandleIMSReply: Incomplete headers for '%s'\n",
491 if (result
.length
+ context
->reqsize
>= HTTP_REQBUF_SZ
) {
492 /* will not get any bigger than that */
494 ("clientHandleIMSReply: Reply is too large '%s', using old entry\n",
496 /* use old entry, this repeats the code abovez */
497 http
->logType
= LOG_TCP_REFRESH_FAIL_HIT
;
498 clientRemoveStoreReference(context
, &context
->sc
, &entry
);
499 entry
= http
->entry
= http
->old_entry
;
500 /* Get the old request back */
501 context
->restoreState(http
);
504 context
->reqofs
+= result
.length
;
505 context
->waitForMoreData(result
);
509 if (clientGetsOldEntry(entry
, http
->old_entry
, http
->request
)) {
510 /* We initiated the IMS request and the IMS is compatible with
511 * our object. As the client is not expecting
512 * 304, so put the good one back. First, make sure the old entry
513 * headers have been loaded from disk. */
514 clientStreamNode
*next
= (clientStreamNode
*)context
->http
->client_stream
.head
->next
->data
;
515 StoreIOBuffer tempresult
;
516 oldentry
= http
->old_entry
;
517 http
->logType
= LOG_TCP_REFRESH_HIT
;
518 if (httpReplyValidatorsMatch(entry
->getReply(),
519 http
->old_entry
->getReply())) {
520 if (oldentry
->mem_obj
->request
== NULL
) {
521 oldentry
->mem_obj
->request
= requestLink(entry
->mem_obj
->request
);
524 /* Don't memcpy() the whole reply structure here. For example,
525 * www.thegist.com (Netscape/1.13) returns a content-length for
526 * 304's which seems to be the length of the 304 HEADERS!!! and
527 * not the body they refer to. */
528 httpReplyUpdateOnNotModified((HttpReply
*)oldentry
->getReply(), entry
->getReply());
529 storeTimestampsSet(oldentry
);
530 clientRemoveStoreReference(context
, &context
->sc
, &entry
);
531 oldentry
->timestamp
= squid_curtime
;
532 if (unlink_request
) {
533 requestUnlink(oldentry
->mem_obj
->request
);
534 oldentry
->mem_obj
->request
= NULL
;
537 /* Get the old request back */
538 context
->restoreState(http
);
540 /* here the data to send is in the next nodes buffers already */
541 assert(!EBIT_TEST(entry
->flags
, ENTRY_ABORTED
));
542 tempresult
.length
= context
->reqsize
;
543 tempresult
.data
= next
->readBuffer
.data
;
544 clientSendMoreData(context
, tempresult
);
547 /* The client gets the new entry,
548 * either as a 304 (they initiated the IMS) or
549 * as a full request from the upstream
550 * The new entry is *not* a 304 reply, or
551 * is a 304 that is incompatible with our cached entities.
553 if (http
->request
->flags
.ims
) {
554 /* The client asked for a IMS, and can deal
556 * XXX TODO: invalidate our object if it's not valid any more.
557 * Send the IMS reply to the client.
559 context
->sendClientUpstreamResponse();
560 } else if (httpReplyValidatorsMatch (entry
->getReply(),
561 http
->old_entry
->getReply())) {
562 /* Our object is usable once updated */
563 /* the client did not ask for IMS, send the whole object
565 /* the client needs to get this reply */
566 StoreIOBuffer tempresult
;
567 http
->logType
= LOG_TCP_REFRESH_MISS
;
568 if (HTTP_NOT_MODIFIED
== entry
->getReply()->sline
.status
) {
569 httpReplyUpdateOnNotModified((HttpReply
*)http
->old_entry
->getReply(),
571 storeTimestampsSet(http
->old_entry
);
572 http
->logType
= LOG_TCP_REFRESH_HIT
;
574 clientRemoveStoreReference(context
, &context
->old_sc
, &http
->old_entry
);
575 /* here the data to send is the data we just recieved */
576 context
->tempBuffer
.offset
= 0;
577 context
->old_reqsize
= 0;
578 /* clientSendMoreData tracks the offset as well.
579 * Force it back to zero */
581 assert(!EBIT_TEST(entry
->flags
, ENTRY_ABORTED
));
582 /* TODO: provide SendMoreData with the ready parsed reply */
583 tempresult
.length
= context
->reqsize
;
584 tempresult
.data
= context
->tempbuf
;
585 clientSendMoreData(context
, tempresult
);
587 /* the client asked for the whole object, and
588 * 1) our object was stale
589 * 2) our internally generated IMS failed to validate
590 * 3) the server sent incompatible headers in it's reply
592 http
->logType
= LOG_TCP_REFRESH_MISS
;
593 clientProcessMiss(context
);
594 /* We start over for everything except IMS because:
595 * 1) HEAD requests will go straight through now
596 * 2) GET requests will go straight through now
597 * 3) IMS requests are a corner case. If the server
598 * decided to give us different data, we should give
599 * that to the client, which means returning our IMS request.
605 extern "C" CSR clientGetMoreData
;
606 extern "C" CSD clientReplyDetach
;
609 * clientCacheHit should only be called until the HTTP reply headers
610 * have been parsed. Normally this should be a single call, but
611 * it might take more than one. As soon as we have the headers,
612 * we hand off to clientSendMoreData, clientProcessExpired, or
616 clientCacheHit(void *data
, StoreIOBuffer result
)
618 clientReplyContext
*context
= (clientReplyContext
*)data
;
619 clientHttpRequest
*http
= context
->http
;
620 StoreEntry
*e
= http
->entry
;
621 request_t
*r
= http
->request
;
622 debug(88, 3) ("clientCacheHit: %s, %ud bytes\n", http
->uri
, (unsigned int)result
.length
);
623 if (http
->entry
== NULL
) {
624 debug(88, 3) ("clientCacheHit: request aborted\n");
626 } else if (result
.flags
.error
) {
627 /* swap in failure */
628 debug(88, 3) ("clientCacheHit: swapin failure for %s\n", http
->uri
);
629 http
->logType
= LOG_TCP_SWAPFAIL_MISS
;
630 clientRemoveStoreReference(context
, &context
->sc
, &http
->entry
);
631 clientProcessMiss(context
);
634 if (result
.length
== 0) {
635 /* the store couldn't get enough data from the file for us to id the
638 /* treat as a miss */
639 http
->logType
= LOG_TCP_MISS
;
640 clientProcessMiss(context
);
643 assert(!EBIT_TEST(e
->flags
, ENTRY_ABORTED
));
644 /* update size of the request */
645 context
->reqsize
= result
.length
+ context
->reqofs
;
646 if (e
->getReply()->sline
.status
== 0) {
648 * we don't have full reply headers yet; either wait for more or
649 * punt to clientProcessMiss.
651 if (e
->mem_status
== IN_MEMORY
|| e
->store_status
== STORE_OK
) {
652 clientProcessMiss(context
);
653 } else if (result
.length
+ context
->reqofs
>= HTTP_REQBUF_SZ
654 && http
->out
.offset
== 0) {
655 clientProcessMiss(context
);
657 clientStreamNode
*next
;
658 StoreIOBuffer tempBuffer
;
659 debug(88, 3) ("clientCacheHit: waiting for HTTP reply headers\n");
660 context
->reqofs
+= result
.length
;
661 assert(context
->reqofs
<= HTTP_REQBUF_SZ
);
662 /* get the next users' buffer */
663 /* FIXME: HTTP_REQBUF_SZ must be wrong here ??!
665 next
= (clientStreamNode
*)context
->http
->client_stream
.head
->next
->data
;
666 tempBuffer
.offset
= http
->out
.offset
+ context
->reqofs
;
667 tempBuffer
.length
= HTTP_REQBUF_SZ
;
668 tempBuffer
.data
= next
->readBuffer
.data
+ context
->reqofs
;
669 storeClientCopy(context
->sc
, e
,
670 tempBuffer
, clientCacheHit
, context
);
675 * Got the headers, now grok them
677 assert(http
->logType
== LOG_TCP_HIT
);
678 switch (varyEvaluateMatch(e
, r
)) {
680 /* No variance detected. Continue as normal */
683 /* This is the correct entity for this request. Continue */
684 debug(88, 2) ("clientProcessHit: Vary MATCH!\n");
687 /* This is not the correct entity for this request. We need
688 * to requery the cache.
690 clientRemoveStoreReference(context
, &context
->sc
, &http
->entry
);
692 /* Note: varyEvalyateMatch updates the request with vary information
693 * so we only get here once. (it also takes care of cancelling loops)
695 debug(88, 2) ("clientProcessHit: Vary detected!\n");
696 clientGetMoreData(context
->ourNode
, http
);
699 /* varyEvaluateMatch found a object loop. Process as miss */
700 debug(88, 1) ("clientProcessHit: Vary object loop!\n");
701 clientProcessMiss(context
);
704 if (r
->method
== METHOD_PURGE
) {
705 clientRemoveStoreReference(context
, &context
->sc
, &http
->entry
);
707 context
->purgeRequest();
710 if (storeCheckNegativeHit(e
)) {
711 http
->logType
= LOG_TCP_NEGATIVE_HIT
;
712 clientSendMoreData(context
, result
);
713 } else if (!Config
.onoff
.offline
&& refreshCheckHTTP(e
, r
) && !http
->flags
.internal
) {
714 debug(88, 5) ("clientCacheHit: in refreshCheck() block\n");
716 * We hold a stale copy; it needs to be validated
719 * The 'need_validation' flag is used to prevent forwarding
720 * loops between siblings. If our copy of the object is stale,
721 * then we should probably only use parents for the validation
722 * request. Otherwise two siblings could generate a loop if
723 * both have a stale version of the object.
725 r
->flags
.need_validation
= 1;
726 if (e
->lastmod
< 0) {
728 * Previous reply didn't have a Last-Modified header,
729 * we cannot revalidate it.
731 http
->logType
= LOG_TCP_MISS
;
732 clientProcessMiss(context
);
733 } else if (r
->flags
.nocache
) {
735 * This did not match a refresh pattern that overrides no-cache
736 * we should honour the client no-cache header.
738 http
->logType
= LOG_TCP_CLIENT_REFRESH_MISS
;
739 clientProcessMiss(context
);
740 } else if (r
->protocol
== PROTO_HTTP
) {
742 * Object needs to be revalidated
743 * XXX This could apply to FTP as well, if Last-Modified is known.
745 http
->logType
= LOG_TCP_REFRESH_MISS
;
746 clientProcessExpired(context
);
749 * We don't know how to re-validate other protocols. Handle
750 * them as if the object has expired.
752 http
->logType
= LOG_TCP_MISS
;
753 clientProcessMiss(context
);
755 } else if (r
->flags
.ims
) {
757 * Handle If-Modified-Since requests from the client
759 if (e
->getReply()->sline
.status
!= HTTP_OK
) {
760 debug(88, 4) ("clientCacheHit: Reply code %d != 200\n",
761 e
->getReply()->sline
.status
);
762 http
->logType
= LOG_TCP_MISS
;
763 clientProcessMiss(context
);
764 } else if (modifiedSince(e
, http
->request
)) {
765 http
->logType
= LOG_TCP_IMS_HIT
;
766 clientSendMoreData(context
, result
);
768 time_t timestamp
= e
->timestamp
;
769 HttpReply
*temprep
= httpReplyMake304 (e
->getReply());
770 http
->logType
= LOG_TCP_IMS_HIT
;
771 clientRemoveStoreReference(context
, &context
->sc
, &http
->entry
);
773 clientCreateStoreEntry(context
, http
->request
->method
,
776 * Copy timestamp from the original entry so the 304
777 * reply has a meaningful Age: header.
779 e
->timestamp
= timestamp
;
780 httpReplySwapOut (temprep
, e
);
782 /* TODO: why put this in the store and then serialise it and then parse it again.
783 * Simply mark the request complete in our context and
784 * write the reply struct to the client side
786 triggerInitialStoreReadWithClientParameters(context
, http
);
790 * plain ol' cache hit
792 if (e
->mem_status
== IN_MEMORY
)
793 http
->logType
= LOG_TCP_MEM_HIT
;
794 else if (Config
.onoff
.offline
)
795 http
->logType
= LOG_TCP_OFFLINE_HIT
;
796 clientSendMoreData(context
, result
);
801 * Prepare to fetch the object as it's a cache miss of some kind.
804 clientProcessMiss(clientReplyContext
* context
)
806 clientHttpRequest
*http
= context
->http
;
807 char *url
= http
->uri
;
808 request_t
*r
= http
->request
;
809 ErrorState
*err
= NULL
;
810 debug(88, 4) ("clientProcessMiss: '%s %s'\n",
811 RequestMethodStr
[r
->method
], url
);
813 * We might have a left-over StoreEntry from a failed cache hit
817 if (EBIT_TEST(http
->entry
->flags
, ENTRY_SPECIAL
)) {
818 debug(88, 0) ("clientProcessMiss: miss on a special object (%s).\n",
820 debug(88, 0) ("\tlog_type = %s\n", log_tags
[http
->logType
]);
821 storeEntryDump(http
->entry
, 1);
823 clientRemoveStoreReference(context
, &context
->sc
, &http
->entry
);
825 if (r
->method
== METHOD_PURGE
) {
826 context
->purgeRequest();
829 if (clientOnlyIfCached(http
)) {
830 clientProcessOnlyIfCachedMiss(context
);
834 * Deny loops when running in accelerator/transproxy mode.
836 if (http
->flags
.accel
&& r
->flags
.loopdetect
) {
837 http
->al
.http
.code
= HTTP_FORBIDDEN
;
839 clientBuildError(ERR_ACCESS_DENIED
, HTTP_FORBIDDEN
, NULL
,
840 &http
->conn
->peer
.sin_addr
, http
->request
);
842 clientCreateStoreEntry(context
, r
->method
, request_flags());
843 errorAppendEntry(http
->entry
, err
);
844 triggerInitialStoreReadWithClientParameters(context
, http
);
847 assert(http
->out
.offset
== 0);
848 http
->entry
= clientCreateStoreEntry(context
, r
->method
, r
->flags
);
849 triggerInitialStoreReadWithClientParameters(context
, http
);
850 if (http
->redirect
.status
) {
851 HttpReply
*rep
= httpReplyCreate();
852 #if LOG_TCP_REDIRECTS
853 http
->logType
= LOG_TCP_REDIRECT
;
855 storeReleaseRequest(http
->entry
);
856 httpRedirectReply(rep
, http
->redirect
.status
,
857 http
->redirect
.location
);
858 httpReplySwapOut(rep
, http
->entry
);
859 http
->entry
->complete();
862 if (http
->flags
.internal
)
863 r
->protocol
= PROTO_INTERNAL
;
864 fwdStart(http
->conn
? http
->conn
->fd
: -1, http
->entry
, r
);
869 * client issued a request with an only-if-cached cache-control directive;
870 * we did not find a cached object that can be returned without
871 * contacting other servers;
872 * respond with a 504 (Gateway Timeout) as suggested in [RFC 2068]
875 clientProcessOnlyIfCachedMiss(clientReplyContext
* context
)
877 clientHttpRequest
*http
= context
->http
;
878 char *url
= http
->uri
;
879 request_t
*r
= http
->request
;
880 ErrorState
*err
= NULL
;
881 debug(88, 4) ("clientProcessOnlyIfCachedMiss: '%s %s'\n",
882 RequestMethodStr
[r
->method
], url
);
883 http
->al
.http
.code
= HTTP_GATEWAY_TIMEOUT
;
884 err
= clientBuildError(ERR_ONLY_IF_CACHED_MISS
, HTTP_GATEWAY_TIMEOUT
, NULL
,
885 &http
->conn
->peer
.sin_addr
, http
->request
);
886 clientRemoveStoreReference(context
, &context
->sc
, &http
->entry
);
887 startError(context
, http
, err
);
891 clientReplyContext::purgeRequestFindObjectToPurge()
893 /* Try to find a base entry */
894 http
->flags
.purging
= 1;
896 StoreEntry::getPublicByRequestMethod(this, http
->request
, METHOD_GET
);
900 clientReplyContext::created(StoreEntry
*newEntry
)
902 if (lookingforstore
== 1)
903 purgeFoundGet(newEntry
);
904 else if (lookingforstore
== 2)
905 purgeFoundHead(newEntry
);
906 else if (lookingforstore
== 3)
907 purgeDoPurgeGet(newEntry
);
908 else if (lookingforstore
== 4)
909 purgeDoPurgeHead(newEntry
);
910 else if (lookingforstore
== 5)
911 identifyFoundObject(newEntry
);
915 clientReplyContext::purgeFoundGet(StoreEntry
*newEntry
)
917 if (newEntry
->isNull()) {
919 StoreEntry::getPublicByRequestMethod(this, http
->request
, METHOD_HEAD
);
921 purgeFoundObject (newEntry
);
925 clientReplyContext::purgeFoundHead(StoreEntry
*newEntry
)
927 if (newEntry
->isNull())
930 purgeFoundObject (newEntry
);
934 clientReplyContext::purgeFoundObject(StoreEntry
*entry
)
936 assert (entry
&& !entry
->isNull());
937 StoreIOBuffer tempBuffer
;
938 /* Swap in the metadata */
940 storeLockObject(http
->entry
);
941 storeCreateMemObject(http
->entry
, http
->uri
, http
->log_uri
);
942 http
->entry
->mem_obj
->method
= http
->request
->method
;
943 sc
= storeClientListAdd(http
->entry
, this);
944 http
->logType
= LOG_TCP_HIT
;
946 tempBuffer
.offset
= http
->out
.offset
;
947 clientStreamNode
*next
= (clientStreamNode
*)http
->client_stream
.head
->next
->data
;
948 tempBuffer
.length
= next
->readBuffer
.length
;
949 tempBuffer
.data
= next
->readBuffer
.data
;
950 storeClientCopy(sc
, http
->entry
,
951 tempBuffer
, clientCacheHit
, this);
955 clientReplyContext::purgeRequest()
957 debug(88, 3) ("Config2.onoff.enable_purge = %d\n",
958 Config2
.onoff
.enable_purge
);
959 if (!Config2
.onoff
.enable_purge
) {
960 http
->logType
= LOG_TCP_DENIED
;
962 clientBuildError(ERR_ACCESS_DENIED
, HTTP_FORBIDDEN
, NULL
,
963 &http
->conn
->peer
.sin_addr
, http
->request
);
964 startError(this, http
, err
);
967 /* Release both IP cache */
968 ipcacheInvalidate(http
->request
->host
);
970 if (!http
->flags
.purging
)
971 purgeRequestFindObjectToPurge();
977 clientReplyContext::purgeDoMissPurge()
979 http
->logType
= LOG_TCP_MISS
;
981 StoreEntry::getPublicByRequestMethod(this,http
->request
, METHOD_GET
);
985 clientReplyContext::purgeDoPurgeGet(StoreEntry
*newEntry
)
988 /* Move to new() when that is created */
989 purgeStatus
= HTTP_NOT_FOUND
;
991 if (!newEntry
->isNull()) {
992 /* Release the cached URI */
993 debug(88, 4) ("clientPurgeRequest: GET '%s'\n", storeUrl(newEntry
));
994 storeRelease(newEntry
);
995 purgeStatus
= HTTP_OK
;
998 StoreEntry::getPublicByRequestMethod(this, http
->request
, METHOD_HEAD
);
1002 clientReplyContext::purgeDoPurgeHead(StoreEntry
*newEntry
)
1005 debug(88, 4) ("clientPurgeRequest: HEAD '%s'\n", storeUrl(newEntry
));
1006 storeRelease(newEntry
);
1007 purgeStatus
= HTTP_OK
;
1010 http_version_t version
;
1012 /* And for Vary, release the base URI if none of the headers was included in the request */
1013 if (http
->request
->vary_headers
1014 && !strstr(http
->request
->vary_headers
, "=")) {
1015 StoreEntry
*entry
= storeGetPublic(urlCanonical(http
->request
), METHOD_GET
);
1017 debug(88, 4) ("clientPurgeRequest: Vary GET '%s'\n",
1019 storeRelease(entry
);
1020 purgeStatus
= HTTP_OK
;
1022 entry
= storeGetPublic(urlCanonical(http
->request
), METHOD_HEAD
);
1024 debug(88, 4) ("clientPurgeRequest: Vary HEAD '%s'\n",
1026 storeRelease(entry
);
1027 purgeStatus
= HTTP_OK
;
1031 * Make a new entry to hold the reply to be written
1034 /* FIXME: This doesn't need to go through the store. Simply
1035 * push down the client chain
1038 clientCreateStoreEntry(this, http
->request
->method
,
1040 triggerInitialStoreReadWithClientParameters(this, http
);
1041 r
= httpReplyCreate();
1042 httpBuildVersion(&version
, 1, 0);
1043 httpReplySetHeaders(r
, version
, purgeStatus
, NULL
, NULL
, 0, 0, -1);
1044 httpReplySwapOut(r
, http
->entry
);
1045 http
->entry
->complete();
1049 clientTraceReply(clientStreamNode
* node
, clientReplyContext
* context
)
1052 http_version_t version
;
1053 clientStreamNode
*next
= (clientStreamNode
*)node
->node
.next
->data
;
1054 StoreIOBuffer tempBuffer
;
1055 assert(context
->http
->request
->max_forwards
== 0);
1056 context
->http
->entry
=
1057 clientCreateStoreEntry(context
, context
->http
->request
->method
,
1059 tempBuffer
.offset
= next
->readBuffer
.offset
+ context
->headers_sz
;
1060 tempBuffer
.length
= next
->readBuffer
.length
;
1061 tempBuffer
.data
= next
->readBuffer
.data
;
1062 storeClientCopy(context
->sc
, context
->http
->entry
,
1063 tempBuffer
, clientSendMoreData
, context
);
1064 storeReleaseRequest(context
->http
->entry
);
1065 storeBuffer(context
->http
->entry
);
1066 rep
= httpReplyCreate();
1067 httpBuildVersion(&version
, 1, 0);
1068 httpReplySetHeaders(rep
, version
, HTTP_OK
, NULL
, "text/plain",
1069 httpRequestPrefixLen(context
->http
->request
), 0, squid_curtime
);
1070 httpReplySwapOut(rep
, context
->http
->entry
);
1071 httpRequestSwapOut(context
->http
->request
, context
->http
->entry
);
1072 context
->http
->entry
->complete();
1075 #define SENDING_BODY 0
1076 #define SENDING_HDRSONLY 1
1078 clientCheckTransferDone(clientReplyContext
* context
)
1080 StoreEntry
*entry
= context
->http
->entry
;
1084 * For now, 'done_copying' is used for special cases like
1085 * Range and HEAD requests.
1087 if (context
->http
->flags
.done_copying
)
1090 * Handle STORE_OK objects.
1091 * objectLen(entry) will be set proprely.
1092 * RC: Does objectLen(entry) include the Headers?
1095 if (entry
->store_status
== STORE_OK
) {
1096 return context
->storeOKTransferDone();
1098 return context
->storeNotOKTransferDone();
1103 clientReplyContext::storeOKTransferDone() const
1105 if (http
->out
.offset
>= objectLen(http
->entry
) - headers_sz
)
1111 clientReplyContext::storeNotOKTransferDone() const
1114 * Now, handle STORE_PENDING objects
1116 MemObject
*mem
= http
->entry
->mem_obj
;
1117 assert(mem
!= NULL
);
1118 assert(http
->request
!= NULL
);
1119 /* mem->reply was wrong because it uses the UPSTREAM header length!!! */
1120 http_reply
const *reply
= mem
->getReply();
1121 if (headers_sz
== 0)
1122 /* haven't found end of headers yet */
1125 int sending
= SENDING_BODY
;
1126 if (reply
->sline
.status
== HTTP_NO_CONTENT
||
1127 reply
->sline
.status
== HTTP_NOT_MODIFIED
||
1128 reply
->sline
.status
< HTTP_OK
||
1129 http
->request
->method
== METHOD_HEAD
)
1130 sending
= SENDING_HDRSONLY
;
1132 * Figure out how much data we are supposed to send.
1133 * If we are sending a body and we don't have a content-length,
1134 * then we must wait for the object to become STORE_OK.
1136 if (reply
->content_length
< 0)
1139 size_t expectedLength
= http
->out
.headers_sz
+ reply
->content_length
;
1140 if (http
->out
.size
< expectedLength
)
1149 clientGotNotEnough(clientHttpRequest
const *http
)
1152 httpReplyBodySize(http
->request
->method
, http
->entry
->mem_obj
->getReply());
1154 if (http
->out
.offset
< cl
)
1160 /* A write has completed, what is the next status based on the
1161 * canonical request data?
1162 * 1 something is wrong
1163 * 0 nothing is wrong.
1167 clientHttpRequestStatus(int fd
, clientHttpRequest
const *http
)
1169 #if SIZEOF_SIZE_T == 4
1170 if (http
->out
.size
> 0x7FFF0000) {
1171 debug(88, 1) ("WARNING: closing FD %d to prevent counter overflow\n",
1173 debug(88, 1) ("\tclient %s\n",
1174 inet_ntoa(http
->conn
? http
->conn
->peer
.sin_addr
: no_addr
));
1175 debug(88, 1) ("\treceived %d bytes\n", (int) http
->out
.size
);
1176 debug(88, 1) ("\tURI %s\n", http
->log_uri
);
1180 #if SIZEOF_OFF_T == 4
1181 if (http
->out
.offset
> 0x7FFF0000) {
1182 debug(88, 1) ("WARNING: closing FD %d to prevent counter overflow\n",
1184 debug(88, 1) ("\tclient %s\n",
1185 inet_ntoa(http
->conn
? http
->conn
->peer
.sin_addr
: no_addr
));
1186 debug(88, 1) ("\treceived %d bytes (offset %d)\n", (int) http
->out
.size
,
1187 (int) http
->out
.offset
);
1188 debug(88, 1) ("\tURI %s\n", http
->log_uri
);
1196 * *http is a valid structure.
1197 * fd is either -1, or an open fd.
1199 * TODO: enumify this
1201 * This function is used by any http request sink, to determine the status
1204 clientStream_status_t
1205 clientReplyStatus(clientStreamNode
* aNode
, clientHttpRequest
* http
)
1207 clientReplyContext
*context
= (clientReplyContext
*)aNode
->data
;
1209 /* Here because lower nodes don't need it */
1210 if (http
->entry
== NULL
)
1211 return STREAM_FAILED
; /* yuck, but what can we do? */
1212 if (EBIT_TEST(http
->entry
->flags
, ENTRY_ABORTED
))
1213 /* TODO: Could upstream read errors (result.flags.error) be
1214 * lost, and result in undersize requests being considered
1215 * complete. Should we tcp reset such connections ?
1217 return STREAM_FAILED
;
1218 if ((done
= clientCheckTransferDone(context
)) != 0 || context
->flags
.complete
) {
1219 debug(88, 5) ("clientReplyStatus: transfer is DONE\n");
1220 /* Ok we're finished, but how? */
1221 if (httpReplyBodySize(http
->request
->method
,
1222 http
->entry
->getReply()) < 0) {
1223 debug(88, 5) ("clientReplyStatus: closing, content_length < 0\n");
1224 return STREAM_FAILED
;
1227 debug(88, 5) ("clientReplyStatus: closing, !done, but read 0 bytes\n");
1228 return STREAM_FAILED
;
1230 if (clientGotNotEnough(http
)) {
1231 debug(88, 5) ("clientReplyStatus: client didn't get all it expected\n");
1232 return STREAM_UNPLANNED_COMPLETE
;
1234 if (http
->request
->flags
.proxy_keepalive
) {
1235 debug(88, 5) ("clientReplyStatus: stream complete and can keepalive\n");
1236 return STREAM_COMPLETE
;
1238 debug(88, 5) ("clientReplyStatus: stream was not expected to complete!\n");
1239 return STREAM_UNPLANNED_COMPLETE
;
1241 if (clientReplyBodyTooLarge(http
->entry
->getReply(), http
->out
.offset
)) {
1242 debug(88, 5) ("clientReplyStatus: client reply body is too large\n");
1243 return STREAM_FAILED
;
1248 /* Responses with no body will not have a content-type header,
1249 * which breaks the rep_mime_type acl, which
1250 * coincidentally, is the most common acl for reply access lists.
1251 * A better long term fix for this is to allow acl matchs on the various
1252 * status codes, and then supply a default ruleset that puts these
1253 * codes before any user defines access entries. That way the user
1254 * can choose to block these responses where appropriate, but won't get
1255 * mysterious breakages.
1258 clientAlwaysAllowResponse(http_status sline
)
1262 case HTTP_SWITCHING_PROTOCOLS
:
1263 case HTTP_PROCESSING
:
1264 case HTTP_NO_CONTENT
:
1265 case HTTP_NOT_MODIFIED
:
1275 clientObeyConnectionHeader(clientHttpRequest
* http
, HttpReply
* rep
)
1277 HttpHeader
*hdr
= &rep
->header
;
1278 if (httpHeaderHas(hdr
, HDR_CONNECTION
)) {
1279 /* anything that matches Connection list member will be deleted */
1280 String strConnection
= httpHeaderGetList(hdr
, HDR_CONNECTION
);
1281 const HttpHeaderEntry
*e
;
1282 HttpHeaderPos pos
= HttpHeaderInitPos
;
1284 * think: on-average-best nesting of the two loops (hdrEntry
1285 * and strListItem) @?@
1288 * maybe we should delete standard stuff ("keep-alive","close")
1289 * from strConnection first?
1291 while ((e
= httpHeaderGetEntry(hdr
, &pos
))) {
1292 if (strListIsMember(&strConnection
, e
->name
.buf(), ','))
1293 httpHeaderDelAt(hdr
, pos
);
1295 httpHeaderDelById(hdr
, HDR_CONNECTION
);
1296 strConnection
.clean();
1301 * filters out unwanted entries from original reply header
1302 * adds extra entries if we have more info than origin server
1303 * adds Squid specific entries
1306 clientBuildReplyHeader(clientReplyContext
*context
, HttpReply
* rep
)
1308 clientHttpRequest
* http
= context
->http
;
1309 HttpHeader
*hdr
= &rep
->header
;
1310 int is_hit
= logTypeIsATcpHit(http
->logType
);
1311 request_t
*request
= http
->request
;
1312 #if DONT_FILTER_THESE
1313 /* but you might want to if you run Squid as an HTTP accelerator */
1314 /* httpHeaderDelById(hdr, HDR_ACCEPT_RANGES); */
1315 httpHeaderDelById(hdr
, HDR_ETAG
);
1317 httpHeaderDelById(hdr
, HDR_PROXY_CONNECTION
);
1318 /* here: Keep-Alive is a field-name, not a connection directive! */
1319 httpHeaderDelByName(hdr
, "Keep-Alive");
1320 /* remove Set-Cookie if a hit */
1322 httpHeaderDelById(hdr
, HDR_SET_COOKIE
);
1323 clientObeyConnectionHeader(http
, rep
);
1324 // if (request->range)
1325 // clientBuildRangeHeader(http, rep);
1327 * Add a estimated Age header on cache hits.
1331 * Remove any existing Age header sent by upstream caches
1332 * (note that the existing header is passed along unmodified
1335 httpHeaderDelById(hdr
, HDR_AGE
);
1337 * This adds the calculated object age. Note that the details of the
1338 * age calculation is performed by adjusting the timestamp in
1339 * storeTimestampsSet(), not here.
1341 * BROWSER WORKAROUND: IE sometimes hangs when receiving a 0 Age
1342 * header, so don't use it unless there is a age to report. Please
1343 * note that Age is only used to make a conservative estimation of
1344 * the objects age, so a Age: 0 header does not add any useful
1345 * information to the reply in any case.
1347 if (NULL
== http
->entry
)
1349 else if (http
->entry
->timestamp
< 0)
1351 else if (http
->entry
->timestamp
< squid_curtime
) {
1352 httpHeaderPutInt(hdr
, HDR_AGE
,
1353 squid_curtime
- http
->entry
->timestamp
);
1354 /* Signal old objects. NB: rfc 2616 is not clear,
1355 * by implication, on whether we should do this to all
1356 * responses, or only cache hits.
1357 * 14.46 states it ONLY applys for heuristically caclulated
1358 * freshness values, 13.2.4 doesn't specify the same limitation.
1359 * We interpret RFC 2616 under the combination.
1361 /* TODO: if maxage or s-maxage is present, don't do this */
1362 if (squid_curtime
- http
->entry
->timestamp
>= 86400) {
1364 snprintf (tempbuf
, sizeof(tempbuf
), "%s %s %s",
1366 "This cache hit is still fresh and more than 1 day old");
1367 httpHeaderPutStr(hdr
, HDR_WARNING
, tempbuf
);
1372 /* Filter unproxyable authentication types */
1373 if (http
->logType
!= LOG_TCP_DENIED
&&
1374 (httpHeaderHas(hdr
, HDR_WWW_AUTHENTICATE
) || httpHeaderHas(hdr
, HDR_PROXY_AUTHENTICATE
))) {
1375 HttpHeaderPos pos
= HttpHeaderInitPos
;
1377 while ((e
= httpHeaderGetEntry(hdr
, &pos
))) {
1378 if (e
->id
== HDR_WWW_AUTHENTICATE
|| e
->id
== HDR_PROXY_AUTHENTICATE
) {
1379 const char *value
= e
->value
.buf();
1380 if ((strncasecmp(value
, "NTLM", 4) == 0 &&
1381 (value
[4] == '\0' || value
[4] == ' '))
1383 (strncasecmp(value
, "Negotiate", 9) == 0 &&
1384 (value
[9] == '\0' || value
[9] == ' ')))
1385 httpHeaderDelAt(hdr
, pos
);
1389 /* Handle authentication headers */
1390 if (request
->auth_user_request
)
1391 authenticateFixHeader(rep
, request
->auth_user_request
, request
,
1392 http
->flags
.accel
, 0);
1393 /* Append X-Cache */
1394 httpHeaderPutStrf(hdr
, HDR_X_CACHE
, "%s from %s",
1395 is_hit
? "HIT" : "MISS", getMyHostname());
1396 #if USE_CACHE_DIGESTS
1397 /* Append X-Cache-Lookup: -- temporary hack, to be removed @?@ @?@ */
1398 httpHeaderPutStrf(hdr
, HDR_X_CACHE_LOOKUP
, "%s from %s:%d",
1399 context
->lookup_type
? context
->lookup_type
: "NONE",
1400 getMyHostname(), getMyPort());
1402 if (httpReplyBodySize(request
->method
, rep
) < 0) {
1405 ("clientBuildReplyHeader: can't keep-alive, unknown body size\n");
1406 request
->flags
.proxy_keepalive
= 0;
1410 LOCAL_ARRAY(char, bbuf
, MAX_URL
+ 32);
1411 String strVia
= httpHeaderGetList(hdr
, HDR_VIA
);
1412 snprintf(bbuf
, sizeof(bbuf
), "%d.%d %s",
1413 rep
->sline
.version
.major
,
1414 rep
->sline
.version
.minor
,
1416 strListAdd(&strVia
, bbuf
, ',');
1417 httpHeaderDelById(hdr
, HDR_VIA
);
1418 httpHeaderPutStr(hdr
, HDR_VIA
, strVia
.buf());
1421 /* Signal keep-alive if needed */
1422 httpHeaderPutStr(hdr
,
1423 http
->flags
.accel
? HDR_CONNECTION
: HDR_PROXY_CONNECTION
,
1424 request
->flags
.proxy_keepalive
? "keep-alive" : "close");
1425 #if ADD_X_REQUEST_URI
1427 * Knowing the URI of the request is useful when debugging persistent
1428 * connections in a client; we cannot guarantee the order of http headers,
1429 * but X-Request-URI is likely to be the very last header to ease use from a
1430 * debugger [hdr->entries.count-1].
1432 httpHeaderPutStr(hdr
, HDR_X_REQUEST_URI
,
1433 http
->entry
->mem_obj
->url
? http
->entry
->mem_obj
->url
: http
->uri
);
1435 httpHdrMangleList(hdr
, request
);
1440 clientBuildReply(clientReplyContext
*context
, const char *buf
, size_t size
)
1442 HttpReply
*rep
= httpReplyCreate();
1443 size_t k
= headersEnd(buf
, size
);
1444 if (k
&& httpReplyParse(rep
, buf
, k
)) {
1445 /* enforce 1.0 reply version */
1446 httpBuildVersion(&rep
->sline
.version
, 1, 0);
1447 /* do header conversions */
1448 clientBuildReplyHeader(context
, rep
);
1450 /* parsing failure, get rid of the invalid reply */
1451 httpReplyDestroy(rep
);
1453 /* This is wrong. httpReplyDestroy should to the rep
1454 * for us, and we can destroy our own range info
1456 if (context
->http
->request
->range
) {
1457 /* this will fail and destroy request->range */
1458 // clientBuildRangeHeader(context->http, rep);
1465 clientReplyContext::identifyStoreObject()
1467 request_t
*r
= http
->request
;
1468 if (r
->flags
.cachable
|| r
->flags
.internal
) {
1469 lookingforstore
= 5;
1470 StoreEntry::getPublicByRequest (this, r
);
1472 identifyFoundObject (NullStoreEntry::getInstance());
1476 clientReplyContext::identifyFoundObject(StoreEntry
*newEntry
)
1478 StoreEntry
*e
= newEntry
;
1479 request_t
*r
= http
->request
;
1486 /* Release negatively cached IP-cache entries on reload */
1487 if (r
->flags
.nocache
)
1488 ipcacheInvalidate(r
->host
);
1490 else if (r
->flags
.nocache_hack
)
1491 ipcacheInvalidate(r
->host
);
1493 #if USE_CACHE_DIGESTS
1494 lookup_type
= http
->entry
? "HIT" : "MISS";
1496 if (NULL
== http
->entry
) {
1497 /* this object isn't in the cache */
1498 debug(85, 3) ("clientProcessRequest2: storeGet() MISS\n");
1499 http
->logType
= LOG_TCP_MISS
;
1503 if (Config
.onoff
.offline
) {
1504 debug(85, 3) ("clientProcessRequest2: offline HIT\n");
1505 http
->logType
= LOG_TCP_HIT
;
1509 if (http
->redirect
.status
) {
1510 /* force this to be a miss */
1512 http
->logType
= LOG_TCP_MISS
;
1516 if (!storeEntryValidToSend(e
)) {
1517 debug(85, 3) ("clientProcessRequest2: !storeEntryValidToSend MISS\n");
1519 http
->logType
= LOG_TCP_MISS
;
1523 if (EBIT_TEST(e
->flags
, ENTRY_SPECIAL
)) {
1524 /* Special entries are always hits, no matter what the client says */
1525 debug(85, 3) ("clientProcessRequest2: ENTRY_SPECIAL HIT\n");
1526 http
->logType
= LOG_TCP_HIT
;
1531 if (http
->entry
->store_status
== STORE_PENDING
) {
1532 if (r
->flags
.nocache
|| r
->flags
.nocache_hack
) {
1533 debug(85, 3) ("Clearing no-cache for STORE_PENDING request\n\t%s\n",
1534 storeUrl(http
->entry
));
1535 r
->flags
.nocache
= 0;
1536 r
->flags
.nocache_hack
= 0;
1540 if (r
->flags
.nocache
) {
1541 debug(85, 3) ("clientProcessRequest2: no-cache REFRESH MISS\n");
1543 http
->logType
= LOG_TCP_CLIENT_REFRESH_MISS
;
1547 /* We don't cache any range requests (for now!) -- adrian */
1548 /* RBC - and we won't until the store supports sparse objects.
1549 * I suspec this test is incorrect though, as we can extract ranges from
1550 * a fully cached object
1552 if (r
->flags
.range
) {
1553 /* XXX: test to see if we can satisfy the range with the cached object */
1554 debug(85, 3) ("clientProcessRequest2: force MISS due to range presence\n");
1556 http
->logType
= LOG_TCP_MISS
;
1560 debug(85, 3) ("clientProcessRequest2: default HIT\n");
1561 http
->logType
= LOG_TCP_HIT
;
1565 /* Request more data from the store for the client Stream
1566 * This is *the* entry point to this module.
1569 * This is the head of the list.
1570 * There is at least one more node.
1571 * data context is not null
1574 clientGetMoreData(clientStreamNode
* aNode
, clientHttpRequest
* http
)
1576 clientStreamNode
*next
;
1577 clientReplyContext
*context
;
1578 /* Test preconditions */
1579 assert(aNode
!= NULL
);
1580 assert(cbdataReferenceValid(aNode
));
1581 assert(aNode
->data
!= NULL
);
1582 assert(aNode
->node
.prev
== NULL
);
1583 assert(aNode
->node
.next
!= NULL
);
1584 context
= (clientReplyContext
*)aNode
->data
;
1585 assert(context
->http
== http
);
1587 next
= ( clientStreamNode
*)aNode
->node
.next
->data
;
1588 if (!context
->ourNode
)
1589 context
->ourNode
= aNode
;
1590 /* no cbdatareference, this is only used once, and safely */
1591 if (context
->flags
.storelogiccomplete
) {
1592 StoreIOBuffer tempBuffer
;
1593 tempBuffer
.offset
= next
->readBuffer
.offset
+ context
->headers_sz
;
1594 tempBuffer
.length
= next
->readBuffer
.length
;
1595 tempBuffer
.data
= next
->readBuffer
.data
;
1597 storeClientCopy(context
->sc
, http
->entry
,
1598 tempBuffer
, clientSendMoreData
, context
);
1601 if (context
->http
->request
->method
== METHOD_PURGE
) {
1602 context
->purgeRequest();
1605 if (context
->http
->request
->method
== METHOD_TRACE
) {
1606 if (context
->http
->request
->max_forwards
== 0) {
1607 clientTraceReply(aNode
, context
);
1610 /* continue forwarding, not finished yet. */
1611 http
->logType
= LOG_TCP_MISS
;
1612 context
->doGetMoreData();
1614 context
->identifyStoreObject();
1618 clientReplyContext::doGetMoreData()
1620 /* We still have to do store logic processing - vary, cache hit etc */
1621 if (http
->entry
!= NULL
) {
1622 /* someone found the object in the cache for us */
1623 StoreIOBuffer tempBuffer
;
1624 storeLockObject(http
->entry
);
1625 if (http
->entry
->mem_obj
== NULL
) {
1627 * This if-block exists because we don't want to clobber
1628 * a preexiting mem_obj->method value if the mem_obj
1629 * already exists. For example, when a HEAD request
1630 * is a cache hit for a GET response, we want to keep
1631 * the method as GET.
1633 storeCreateMemObject(http
->entry
, http
->uri
,
1635 http
->entry
->mem_obj
->method
=
1636 http
->request
->method
;
1638 sc
= storeClientListAdd(http
->entry
, this);
1640 delaySetStoreClient(sc
, delayClient(http
));
1642 assert(http
->logType
== LOG_TCP_HIT
);
1644 /* guarantee nothing has been sent yet! */
1645 assert(http
->out
.size
== 0);
1646 assert(http
->out
.offset
== 0);
1647 tempBuffer
.offset
= reqofs
;
1648 tempBuffer
.length
= getNextNode()->readBuffer
.length
;
1649 tempBuffer
.data
= getNextNode()->readBuffer
.data
;
1650 storeClientCopy(sc
, http
->entry
,
1651 tempBuffer
, clientCacheHit
, this);
1653 /* MISS CASE, http->logType is already set! */
1654 clientProcessMiss(this);
1658 /* the next node has removed itself from the stream. */
1660 clientReplyDetach(clientStreamNode
* node
, clientHttpRequest
* http
)
1662 /* detach from the stream */
1663 /* NB: This cbdataFrees our context,
1664 * so the clientSendMoreData callback (if any)
1665 * pending in the store will not trigger
1667 clientStreamDetach(node
, http
);
1671 * accepts chunk of a http message in buf, parses prefix, filters headers and
1672 * such, writes processed message to the message recipient
1675 clientSendMoreData(void *data
, StoreIOBuffer result
)
1677 clientReplyContext::clientSendMoreData (data
, result
);
1681 clientReplyContext::clientSendMoreData (void *data
, StoreIOBuffer result
)
1683 clientReplyContext
*context
= static_cast<clientReplyContext
*>(data
);
1684 context
->sendMoreData (result
);
1688 clientReplyContext::makeThisHead()
1690 /* At least, I think thats what this does */
1691 dlinkDelete(&http
->active
, &ClientActiveRequests
);
1692 dlinkAdd(http
, &http
->active
, &ClientActiveRequests
);
1696 clientReplyContext::errorInStream(StoreIOBuffer
const &result
, size_t const &sizeToProcess
)const
1698 return /* aborted request */
1699 (http
->entry
&& EBIT_TEST(http
->entry
->flags
, ENTRY_ABORTED
)) ||
1700 /* Upstream read error */ (result
.flags
.error
) ||
1701 /* Upstream EOF */ (sizeToProcess
== 0);
1705 clientReplyContext::sendStreamError(StoreIOBuffer
const &result
)
1707 /* call clientWriteComplete so the client socket gets closed */
1708 /* We call into the stream, because we don't know that there is a
1711 debug(88,5)("clientReplyContext::sendStreamError: A stream error has occured, marking as complete and sending no data.\n");
1712 StoreIOBuffer tempBuffer
;
1714 tempBuffer
.flags
.error
= result
.flags
.error
;
1715 clientStreamCallback((clientStreamNode
*)http
->client_stream
.head
->data
, http
, NULL
,
1720 clientReplyContext::pushStreamData(StoreIOBuffer
const &result
, char *source
)
1722 StoreIOBuffer tempBuffer
;
1723 if (result
.length
== 0) {
1724 debug (88,5)("clientReplyContext::pushStreamData: marking request as complete due to 0 length store result\n");
1727 /* REMOVE ME: Only useful for two node streams */
1728 assert(result
.offset
- headers_sz
== ((clientStreamNode
*) http
->client_stream
.tail
->data
)->readBuffer
.offset
);
1729 tempBuffer
.offset
= result
.offset
- headers_sz
;
1730 tempBuffer
.length
= result
.length
;
1731 tempBuffer
.data
= source
;
1732 clientStreamCallback((clientStreamNode
*)http
->client_stream
.head
->data
, http
, NULL
,
1737 clientReplyContext::next() const
1739 assert ( (clientStreamNode
*)http
->client_stream
.head
->next
->data
== getNextNode());
1740 return getNextNode();
1744 clientReplyContext::waitForMoreData (StoreIOBuffer
const &result
)
1746 debug(88,5)("clientReplyContext::waitForMoreData: Waiting for more data to parse reply headers in client side.\n");
1747 /* We don't have enough to parse the metadata yet */
1748 /* TODO: the store should give us out of band metadata and
1749 * obsolete this routine
1751 /* wait for more to arrive */
1756 clientReplyContext::startSendProcess()
1758 debug(88,5)("clientReplyContext::startSendProcess: triggering store read to clientSendMoreData\n");
1759 assert(reqofs
<= HTTP_REQBUF_SZ
);
1760 /* TODO: copy into the supplied buffer */
1761 StoreIOBuffer tempBuffer
;
1762 tempBuffer
.offset
= reqofs
;
1763 tempBuffer
.length
= next()->readBuffer
.length
- reqofs
;
1764 tempBuffer
.data
= next()->readBuffer
.data
+ reqofs
;
1765 storeClientCopy(sc
, http
->entry
,
1766 tempBuffer
, clientSendMoreData
, this);
1770 clientReplyContext::processReplyAccess ()
1772 HttpReply
*rep
= holdingReply
;
1773 holdingReply
= NULL
;
1774 httpReplyBodyBuildSize(http
->request
, rep
, &Config
.ReplyBodySize
);
1775 if (clientReplyBodyTooLarge(rep
, rep
->content_length
)) {
1777 clientBuildError(ERR_TOO_BIG
, HTTP_FORBIDDEN
, NULL
,
1778 http
->conn
? &http
->conn
->peer
.sin_addr
: &no_addr
,
1780 clientRemoveStoreReference(this, &sc
, &http
->entry
);
1781 startError(this, http
, err
);
1782 httpReplyDestroy(rep
);
1785 headers_sz
= rep
->hdr_sz
;
1786 ACLChecklist
*replyChecklist
;
1787 replyChecklist
= clientAclChecklistCreate(Config
.accessList
.reply
, http
);
1788 replyChecklist
->reply
= rep
;
1790 aclNBCheck(replyChecklist
, ProcessReply
, this);
1794 clientReplyContext::ProcessReply (int rv
, void *voidMe
)
1796 clientReplyContext
*me
= static_cast<clientReplyContext
*>(voidMe
);
1797 me
->processReply(rv
);
1801 clientReplyContext::processReply(bool accessAllowed
)
1803 debug(88, 2) ("The reply for %s %s is %s, because it matched '%s'\n",
1804 RequestMethodStr
[http
->request
->method
], http
->uri
,
1805 accessAllowed
? "ALLOWED" : "DENIED",
1806 AclMatchedName
? AclMatchedName
: "NO ACL's");
1807 HttpReply
*rep
= holdingReply
;
1808 holdingReply
= NULL
;
1809 if (!accessAllowed
&& rep
->sline
.status
!= HTTP_FORBIDDEN
1810 && !clientAlwaysAllowResponse(rep
->sline
.status
)) {
1811 /* the if above is slightly broken, but there is no way
1812 * to tell if this is a squid generated error page, or one from
1813 * upstream at this point. */
1816 clientBuildError(ERR_ACCESS_DENIED
, HTTP_FORBIDDEN
, NULL
,
1817 http
->conn
? &http
->conn
->peer
.sin_addr
: &no_addr
,
1819 clientRemoveStoreReference(this, &sc
, &http
->entry
);
1820 startError(this, http
, err
);
1821 httpReplyDestroy(rep
);
1824 ssize_t body_size
= reqofs
- rep
->hdr_sz
;
1825 assert(body_size
>= 0);
1827 ("clientSendMoreData: Appending %d bytes after %d bytes of headers\n",
1828 (int) body_size
, rep
->hdr_sz
);
1829 if (http
->request
->method
== METHOD_HEAD
) {
1830 /* do not forward body for HEAD replies */
1832 http
->flags
.done_copying
= 1;
1835 assert (!flags
.headersSent
);
1836 flags
.headersSent
= true;
1838 StoreIOBuffer tempBuffer
;
1839 char *buf
= next()->readBuffer
.data
;
1840 char *body_buf
= buf
+ rep
->hdr_sz
;
1841 if (next()->readBuffer
.offset
!= 0) {
1842 if (next()->readBuffer
.offset
> body_size
) {
1843 /* Can't use any of the body we recieved. send nothing */
1844 tempBuffer
.length
= 0;
1845 tempBuffer
.data
= NULL
;
1847 tempBuffer
.length
= body_size
- next()->readBuffer
.offset
;
1848 tempBuffer
.data
= body_buf
+ next()->readBuffer
.offset
;
1851 tempBuffer
.length
= body_size
;
1852 tempBuffer
.data
= body_buf
;
1854 /* TODO: move the data in the buffer back by the request header size */
1855 clientStreamCallback((clientStreamNode
*)http
->client_stream
.head
->data
,
1856 http
, rep
, tempBuffer
);
1861 clientReplyContext::sendMoreData (StoreIOBuffer result
)
1863 StoreEntry
*entry
= http
->entry
;
1864 ConnStateData
*conn
= http
->conn
;
1865 int fd
= conn
? conn
->fd
: -1;
1866 HttpReply
*rep
= NULL
;
1867 char *buf
= next()->readBuffer
.data
;
1868 char *body_buf
= buf
;
1870 /* This is always valid until we get the headers as metadata from
1872 * Then it becomes reqofs == next->readBuffer.offset()
1874 assert(reqofs
== 0 || flags
.storelogiccomplete
);
1876 if (flags
.headersSent
&& buf
!= result
.data
) {
1877 /* we've got to copy some data */
1878 assert(result
.length
<= next()->readBuffer
.length
);
1879 xmemcpy(buf
, result
.data
, result
.length
);
1881 } else if (!flags
.headersSent
&&
1882 buf
+ reqofs
!=result
.data
) {
1883 /* we've got to copy some data */
1884 assert(result
.length
+ reqofs
<= next()->readBuffer
.length
);
1885 xmemcpy(buf
+ reqofs
, result
.data
, result
.length
);
1888 /* We've got the final data to start pushing... */
1889 flags
.storelogiccomplete
= 1;
1891 reqofs
+= result
.length
;
1893 assert(reqofs
<= HTTP_REQBUF_SZ
|| flags
.headersSent
);
1894 assert(http
->request
!= NULL
);
1895 /* ESI TODO: remove this assert once everything is stable */
1896 assert(http
->client_stream
.head
->data
1897 && cbdataReferenceValid(http
->client_stream
.head
->data
));
1900 debug(88, 5) ("clientSendMoreData: %s, %d bytes (%u new bytes)\n",
1901 http
->uri
, (int) reqofs
, (unsigned int)result
.length
);
1902 debug(88, 5) ("clientSendMoreData: FD %d '%s', out.offset=%ld \n",
1903 fd
, storeUrl(entry
), (long int) http
->out
.offset
);
1905 /* update size of the request */
1908 if (http
->request
->flags
.resetTCP()) {
1909 /* yuck. FIXME: move to client_side.c */
1911 comm_reset_close(fd
);
1915 if (errorInStream(result
, reqofs
)) {
1916 sendStreamError(result
);
1920 if (flags
.headersSent
) {
1921 pushStreamData (result
, buf
);
1924 /* handle headers */
1925 if (Config
.onoff
.log_mime_hdrs
) {
1927 if ((k
= headersEnd(buf
, reqofs
))) {
1928 safe_free(http
->al
.headers
.reply
);
1929 http
->al
.headers
.reply
= (char *)xcalloc(k
+ 1, 1);
1930 xstrncpy(http
->al
.headers
.reply
, buf
, k
);
1933 rep
= clientBuildReply(this, buf
, reqofs
);
1934 ssize_t body_size
= reqofs
;
1937 holdingBuffer
= result
;
1938 processReplyAccess ();
1940 } else if (reqofs
< HTTP_REQBUF_SZ
&& entry
->store_status
== STORE_PENDING
) {
1941 waitForMoreData(result
);
1943 } else if (http
->request
->method
== METHOD_HEAD
) {
1945 * If we are here, then store_status == STORE_OK and it
1946 * seems we have a HEAD repsponse which is missing the
1947 * empty end-of-headers line (home.mira.net, phttpd/0.99.72
1948 * does this). Because clientBuildReply() fails we just
1949 * call this reply a body, set the done_copying flag and
1952 /* RBC: Note that this is seriously broken, as we *need* the
1953 * metadata to allow further client modules to work. As such
1954 * webservers are seriously broken, this is probably not
1955 * going to get fixed.. perhapos we should remove it?
1957 debug (88,0)("Broken head response - probably phttpd/0.99.72\n");
1958 http
->flags
.done_copying
= 1;
1961 StoreIOBuffer tempBuffer
;
1962 assert(body_buf
&& body_size
);
1963 tempBuffer
.length
= body_size
;
1964 tempBuffer
.data
= body_buf
;
1965 clientStreamCallback((clientStreamNode
*)http
->client_stream
.head
->data
,
1966 http
, NULL
, tempBuffer
);
1968 debug (88,0)("clientReplyContext::sendMoreData: Unable to parse reply headers within a single HTTP_REQBUF_SZ length buffer\n");
1969 StoreIOBuffer tempBuffer
;
1970 tempBuffer
.flags
.error
= 1;
1971 sendStreamError(tempBuffer
);
1974 fatal ("clientReplyContext::sendMoreData: Unreachable code reached \n");
1978 clientReplyBodyTooLarge(HttpReply
const * rep
, ssize_t clen
)
1980 if (0 == rep
->maxBodySize
)
1981 return 0; /* disabled */
1983 return 0; /* unknown */
1984 if ((unsigned int)clen
> rep
->maxBodySize
)
1985 return 1; /* too large */
1990 * returns true if client specified that the object must come from the cache
1991 * without contacting origin server
1994 clientOnlyIfCached(clientHttpRequest
* http
)
1996 const request_t
*r
= http
->request
;
1998 return r
->cache_control
&&
1999 EBIT_TEST(r
->cache_control
->mask
, CC_ONLY_IF_CACHED
);
2002 /* Using this breaks the client layering just a little!
2005 clientCreateStoreEntry(clientReplyContext
* context
, method_t m
,
2006 request_flags flags
)
2008 clientHttpRequest
*h
= context
->http
;
2012 * For erroneous requests, we might not have a h->request,
2013 * so make a fake one.
2015 if (h
->request
== NULL
)
2016 h
->request
= requestLink(requestCreate(m
, PROTO_NONE
, null_string
));
2017 e
= storeCreateEntry(h
->uri
, h
->log_uri
, flags
, m
);
2018 context
->sc
= storeClientListAdd(e
, context
);
2020 delaySetStoreClient(context
->sc
, delayClient(h
));
2022 context
->reqofs
= 0;
2023 context
->reqsize
= 0;
2024 /* I don't think this is actually needed! -- adrian */
2025 /* h->reqbuf = h->norm_reqbuf; */
2026 // assert(h->reqbuf == h->norm_reqbuf);
2027 /* The next line is illegal because we don't know if the client stream
2028 * buffers have been set up
2030 // storeClientCopy(h->sc, e, 0, HTTP_REQBUF_SZ, h->reqbuf,
2031 // clientSendMoreData, context);
2032 /* So, we mark the store logic as complete */
2033 context
->flags
.storelogiccomplete
= 1;
2034 /* and get the caller to request a read, from whereever they are */
2035 /* NOTE: after ANY data flows down the pipe, even one step,
2036 * this function CAN NOT be used to manage errors
2042 clientBuildError(err_type page_id
, http_status status
, char const *url
,
2043 struct in_addr
* src_addr
, request_t
* request
)
2045 ErrorState
*err
= errorCon(page_id
, status
);
2046 err
->src_addr
= *src_addr
;
2048 err
->url
= xstrdup(url
);
2050 err
->request
= requestLink(request
);