3 * $Id: client_side_request.cc,v 1.9 2003/01/23 00:37:18 robertc Exp $
5 * DEBUG: section 85 Client-side Request 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 the Internet
12 * community; see the CONTRIBUTORS file for full details. Many organizations
13 * have provided support for Squid's development; see the SPONSORS file for
14 * full details. Squid is Copyrighted (C) 2001 by the Regents of the
15 * University of California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other sources; see the
17 * CREDITS file for full details.
19 * This program is free software; you can redistribute it and/or modify it under
20 * the terms of the GNU General Public License as published by the Free
21 * Software Foundation; either version 2 of the License, or (at your option)
24 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
25 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
26 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
29 * You should have received a copy of the GNU General Public License along with
30 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
31 * Place, Suite 330, Boston, MA 02111, USA.
37 * General logic of request processing:
39 * We run a series of tests to determine if access will be permitted, and to do
40 * any redirection. Then we call into the result clientStream to retrieve data.
41 * From that point on it's up to reply management.
45 #include "clientStream.h"
46 #include "client_side_request.h"
47 #include "authenticate.h"
48 #include "HttpRequest.h"
51 #define comm_close comm_lingering_close
54 static const char *const crlf
= "\r\n";
56 typedef struct _clientRequestContext
{
57 aclCheck_t
*acl_checklist
; /* need ptr back so we can unreg if needed */
59 clientHttpRequest
*http
;
60 } clientRequestContext
;
62 CBDATA_TYPE(clientRequestContext
);
65 /* clientRequestContext */
66 clientRequestContext
*clientRequestContextNew(clientHttpRequest
*);
67 FREE clientRequestContextFree
;
69 static int checkAccelOnly(clientHttpRequest
*);
70 static void clientAccessCheckDone(int, void *);
71 static int clientCachable(clientHttpRequest
* http
);
72 static int clientHierarchical(clientHttpRequest
* http
);
73 static void clientInterpretRequestHeaders(clientHttpRequest
* http
);
74 static RH clientRedirectDone
;
75 static void clientCheckNoCache(clientRequestContext
* context
);
76 static void clientCheckNoCacheDone(int answer
, void *data
);
77 void clientProcessRequest(clientHttpRequest
*);
78 extern "C" CSR clientGetMoreData
;
79 extern "C" CSS clientReplyStatus
;
80 extern "C" CSD clientReplyDetach
;
81 static void checkFailureRatio(err_type
, hier_code
);
84 clientRequestContextFree(void *data
)
86 clientRequestContext
*context
= (clientRequestContext
*)data
;
87 cbdataReferenceDone(context
->http
);
88 if (context
->acl_checklist
)
89 aclChecklistFree(context
->acl_checklist
);
92 clientRequestContext
*
93 clientRequestContextNew(clientHttpRequest
* http
)
95 clientRequestContext
*rv
;
97 CBDATA_INIT_TYPE_FREECB(clientRequestContext
, clientRequestContextFree
);
98 rv
= cbdataAlloc(clientRequestContext
);
99 rv
->http
= cbdataReference(http
);
103 CBDATA_CLASS_INIT(ClientHttpRequest
);
105 ClientHttpRequest::operator new (size_t size
)
107 assert (size
== sizeof (ClientHttpRequest
));
108 CBDATA_INIT_TYPE(ClientHttpRequest
);
109 ClientHttpRequest
*result
= cbdataAlloc(ClientHttpRequest
);
110 /* Mark result as being owned - we want the refcounter to do the delete
112 cbdataReference(result
);
117 ClientHttpRequest::operator delete (void *address
)
119 ClientHttpRequest
*temp
= static_cast<ClientHttpRequest
*>(address
);
121 /* And allow the memory to be freed */
122 cbdataReferenceDone (temp
);
126 ClientHttpRequest::deleteSelf() const
131 ClientHttpRequest::ClientHttpRequest()
133 /* reset range iterator */
134 start
= current_time
;
138 * This function is designed to serve a fairly specific purpose.
139 * Occasionally our vBNS-connected caches can talk to each other, but not
140 * the rest of the world. Here we try to detect frequent failures which
141 * make the cache unusable (e.g. DNS lookup and connect() failures). If
142 * the failure:success ratio goes above 1.0 then we go into "hit only"
143 * mode where we only return UDP_HIT or UDP_MISS_NOFETCH. Neighbors
144 * will only fetch HITs from us if they are using the ICP protocol. We
145 * stay in this mode for 5 minutes.
147 * Duane W., Sept 16, 1996
150 #define FAILURE_MODE_TIME 300
153 checkFailureRatio(err_type etype
, hier_code hcode
)
155 static double magic_factor
= 100.0;
158 if (hcode
== HIER_NONE
)
160 n_good
= magic_factor
/ (1.0 + request_failure_ratio
);
161 n_bad
= magic_factor
- n_good
;
164 case ERR_CONNECT_FAIL
:
171 request_failure_ratio
= n_bad
/ n_good
;
172 if (hit_only_mode_until
> squid_curtime
)
174 if (request_failure_ratio
< 1.0)
176 debug(33, 0) ("Failure Ratio at %4.2f\n", request_failure_ratio
);
177 debug(33, 0) ("Going into hit-only-mode for %d minutes...\n",
178 FAILURE_MODE_TIME
/ 60);
179 hit_only_mode_until
= squid_curtime
+ FAILURE_MODE_TIME
;
180 request_failure_ratio
= 0.8; /* reset to something less than 1.0 */
183 ClientHttpRequest::~ClientHttpRequest()
185 debug(33, 3) ("httpRequestFree: %s\n", uri
);
186 /* if body_connection !NULL, then ProcessBody has not
187 * found the end of the body yet
189 if (request
&& request
->body_connection
)
190 clientAbortBody(request
); /* abort body transter */
191 /* the ICP check here was erroneous
192 * - storeReleaseRequest was always called if entry was valid
194 assert(logType
< LOG_TYPE_MAX
);
197 checkFailureRatio(request
->errType
, al
.hier
.code
);
199 /* moving to the next connection is handled by the context free */
200 dlinkDelete(&active
, &ClientActiveRequests
);
203 /* Create a request and kick it off */
205 * TODO: Pass in the buffers to be used in the inital Read request, as they are
206 * determined by the user
208 int /* returns nonzero on failure */
209 clientBeginRequest(method_t method
, char const *url
, CSCB
* streamcallback
,
210 CSD
* streamdetach
, void *streamdata
, HttpHeader
const *header
,
211 char *tailbuf
, size_t taillen
)
214 http_version_t http_ver
=
216 clientHttpRequest
*http
= new ClientHttpRequest
;
218 StoreIOBuffer tempBuffer
;
219 http
->http_ver
= http_ver
;
221 http
->start
= current_time
;
222 /* this is only used to adjust the connection offset in client_side.c */
224 tempBuffer
.length
= taillen
;
225 tempBuffer
.data
= tailbuf
;
226 /* client stream setup */
227 clientStreamInit(&http
->client_stream
, clientGetMoreData
, clientReplyDetach
,
228 clientReplyStatus
, clientReplyNewContext(http
), streamcallback
,
229 streamdetach
, streamdata
, tempBuffer
);
230 /* make it visible in the 'current acctive requests list' */
231 dlinkAdd(http
, &http
->active
, &ClientActiveRequests
);
233 http
->flags
.accel
= 1; /* internal requests only makes sense in an
234 * accelerator today. TODO: accept flags ? */
235 /* allow size for url rewriting */
236 url_sz
= strlen(url
) + Config
.appendDomainLen
+ 5;
237 http
->uri
= (char *)xcalloc(url_sz
, 1);
238 strcpy(http
->uri
, url
);
240 if ((request
= urlParse(method
, http
->uri
)) == NULL
) {
241 debug(85, 5) ("Invalid URL: %s\n", http
->uri
);
245 * now update the headers in request with our supplied headers. urLParse
246 * should return a blank header set, but we use Update to be sure of
250 httpHeaderUpdate(&request
->header
, header
, NULL
);
251 http
->log_uri
= xstrdup(urlCanonicalClean(request
));
252 /* http struct now ready */
255 * build new header list *? TODO
257 request
->flags
.accelerated
= http
->flags
.accel
;
258 request
->flags
.internalclient
= 1; /* this is an internally created
259 * request, not subject to acceleration
260 * target overrides */
262 * FIXME? Do we want to detect and handle internal requests of internal
266 /* Internally created requests cannot have bodies today */
267 request
->content_length
= 0;
268 request
->client_addr
= no_addr
;
269 request
->my_addr
= no_addr
; /* undefined for internal requests */
270 request
->my_port
= 0;
271 request
->http_ver
= http_ver
;
272 http
->request
= requestLink(request
);
274 /* optional - skip the access check ? */
275 clientAccessCheck(http
);
280 checkAccelOnly(clientHttpRequest
* http
)
283 * return TRUE if someone makes a proxy request to us and we are in
284 * httpd-accel only mode
286 if (!Config2
.Accel
.on
)
288 if (Config
.onoff
.accel_with_proxy
)
290 if (http
->request
->protocol
== PROTO_CACHEOBJ
)
292 if (http
->flags
.accel
)
294 if (http
->request
->method
== METHOD_PURGE
)
299 /* This is the entry point for external users of the client_side routines */
301 clientAccessCheck(void *data
)
303 clientHttpRequest
*http
= (clientHttpRequest
*)data
;
304 clientRequestContext
*context
= clientRequestContextNew(http
);
305 if (checkAccelOnly(http
)) {
306 /* deny proxy requests in accel_only mode */
308 1) ("clientAccessCheck: proxy request denied in accel_only mode\n");
309 clientAccessCheckDone(ACCESS_DENIED
, context
);
312 context
->acl_checklist
=
313 clientAclChecklistCreate(Config
.accessList
.http
, http
);
314 aclNBCheck(context
->acl_checklist
, clientAccessCheckDone
, context
);
318 clientAccessCheckDone(int answer
, void *data
)
320 clientRequestContext
*context
= (clientRequestContext
*)data
;
321 clientHttpRequest
*http
= context
->http
;
324 char const *proxy_auth_msg
= NULL
;
325 debug(85, 2) ("The request %s %s is %s, because it matched '%s'\n",
326 RequestMethodStr
[http
->request
->method
], http
->uri
,
327 answer
== ACCESS_ALLOWED
? "ALLOWED" : "DENIED",
328 AclMatchedName
? AclMatchedName
: "NO ACL's");
329 proxy_auth_msg
= authenticateAuthUserRequestMessage((http
->conn
330 && http
->conn
->auth_user_request
) ? http
->conn
->
331 auth_user_request
: http
->request
->auth_user_request
);
332 context
->acl_checklist
= NULL
;
333 if (answer
== ACCESS_ALLOWED
) {
334 safe_free(http
->uri
);
335 http
->uri
= xstrdup(urlCanonical(http
->request
));
336 assert(context
->redirect_state
== REDIRECT_NONE
);
337 context
->redirect_state
= REDIRECT_PENDING
;
338 redirectStart(http
, clientRedirectDone
, context
);
341 clientStreamNode
*node
= (clientStreamNode
*)http
->client_stream
.tail
->prev
->data
;
343 debug(85, 5) ("Access Denied: %s\n", http
->uri
);
344 debug(85, 5) ("AclMatchedName = %s\n",
345 AclMatchedName
? AclMatchedName
: "<null>");
346 debug(85, 5) ("Proxy Auth Message = %s\n",
347 proxy_auth_msg
? proxy_auth_msg
: "<null>");
349 * NOTE: get page_id here, based on AclMatchedName because if
350 * USE_DELAY_POOLS is enabled, then AclMatchedName gets clobbered in
351 * the clientCreateStoreEntry() call just below. Pedro Ribeiro
354 page_id
= aclGetDenyInfoPage(&Config
.denyInfoList
, AclMatchedName
);
355 http
->logType
= LOG_TCP_DENIED
;
356 if (answer
== ACCESS_REQ_PROXY_AUTH
|| aclIsProxyAuth(AclMatchedName
)) {
357 if (!http
->flags
.accel
) {
358 /* Proxy authorisation needed */
359 status
= HTTP_PROXY_AUTHENTICATION_REQUIRED
;
361 /* WWW authorisation needed */
362 status
= HTTP_UNAUTHORIZED
;
364 if (page_id
== ERR_NONE
)
365 page_id
= ERR_CACHE_ACCESS_DENIED
;
367 status
= HTTP_FORBIDDEN
;
368 if (page_id
== ERR_NONE
)
369 page_id
= ERR_ACCESS_DENIED
;
371 clientSetReplyToError(node
->data
, page_id
, status
,
372 http
->request
->method
, NULL
,
373 http
->conn
? &http
->conn
->peer
.sin_addr
: &no_addr
, http
->request
,
375 && http
->conn
->auth_user_request
? http
->conn
->
376 auth_user_request
: http
->request
->auth_user_request
);
377 node
= (clientStreamNode
*)http
->client_stream
.tail
->data
;
378 clientStreamRead(node
, http
, node
->readBuffer
);
383 clientCachable(clientHttpRequest
* http
)
385 request_t
*req
= http
->request
;
386 method_t method
= req
->method
;
387 if (req
->protocol
== PROTO_HTTP
)
388 return httpCachable(method
);
389 /* FTP is always cachable */
390 if (req
->protocol
== PROTO_WAIS
)
393 * The below looks questionable: what non HTTP protocols use connect,
394 * trace, put and post? RC
396 if (method
== METHOD_CONNECT
)
398 if (method
== METHOD_TRACE
)
400 if (method
== METHOD_PUT
)
402 if (method
== METHOD_POST
)
403 return 0; /* XXX POST may be cached sometimes.. ignored
405 if (req
->protocol
== PROTO_GOPHER
)
406 return gopherCachable(req
);
407 if (req
->protocol
== PROTO_CACHEOBJ
)
413 clientHierarchical(clientHttpRequest
* http
)
415 const char *url
= http
->uri
;
416 request_t
*request
= http
->request
;
417 method_t method
= request
->method
;
418 const wordlist
*p
= NULL
;
421 * IMS needs a private key, so we can use the hierarchy for IMS only if our
422 * neighbors support private keys
424 if (request
->flags
.ims
&& !neighbors_do_private_keys
)
427 * This is incorrect: authenticating requests can be sent via a hierarchy
428 * (they can even be cached if the correct headers are set on the reply
430 if (request
->flags
.auth
)
432 if (method
== METHOD_TRACE
)
434 if (method
!= METHOD_GET
)
436 /* scan hierarchy_stoplist */
437 for (p
= Config
.hierarchy_stoplist
; p
; p
= p
->next
)
438 if (strstr(url
, p
->key
))
440 if (request
->flags
.loopdetect
)
442 if (request
->protocol
== PROTO_HTTP
)
443 return httpCachable(method
);
444 if (request
->protocol
== PROTO_GOPHER
)
445 return gopherCachable(request
);
446 if (request
->protocol
== PROTO_WAIS
)
448 if (request
->protocol
== PROTO_CACHEOBJ
)
455 clientInterpretRequestHeaders(clientHttpRequest
* http
)
457 request_t
*request
= http
->request
;
458 const HttpHeader
*req_hdr
= &request
->header
;
460 #if !defined(ESI) || defined(USE_USERAGENT_LOG) || defined(USE_REFERER_LOG)
463 request
->imslen
= -1;
464 request
->ims
= httpHeaderGetTime(req_hdr
, HDR_IF_MODIFIED_SINCE
);
465 if (request
->ims
> 0)
466 request
->flags
.ims
= 1;
469 * We ignore Cache-Control as per the Edge Architecture Section 3. See
470 * www.esi.org for more information.
473 if (httpHeaderHas(req_hdr
, HDR_PRAGMA
)) {
474 String s
= httpHeaderGetList(req_hdr
, HDR_PRAGMA
);
475 if (strListIsMember(&s
, "no-cache", ','))
479 request
->cache_control
= httpHeaderGetCc(req_hdr
);
480 if (request
->cache_control
)
481 if (EBIT_TEST(request
->cache_control
->mask
, CC_NO_CACHE
))
484 * Work around for supporting the Reload button in IE browsers when Squid
485 * is used as an accelerator or transparent proxy, by turning accelerated
486 * IMS request to no-cache requests. Now knows about IE 5.5 fix (is
487 * actually only fixed in SP1, but we can't tell whether we are talking to
488 * SP1 or not so all 5.5 versions are treated 'normally').
490 if (Config
.onoff
.ie_refresh
) {
491 if (http
->flags
.accel
&& request
->flags
.ims
) {
492 if ((str
= httpHeaderGetStr(req_hdr
, HDR_USER_AGENT
))) {
493 if (strstr(str
, "MSIE 5.01") != NULL
)
495 else if (strstr(str
, "MSIE 5.0") != NULL
)
497 else if (strstr(str
, "MSIE 4.") != NULL
)
499 else if (strstr(str
, "MSIE 3.") != NULL
)
507 if (Config
.onoff
.reload_into_ims
)
508 request
->flags
.nocache_hack
= 1;
509 else if (refresh_nocache_hack
)
510 request
->flags
.nocache_hack
= 1;
513 request
->flags
.nocache
= 1;
515 /* ignore range header in non-GETs */
516 if (request
->method
== METHOD_GET
) {
517 request
->range
= httpHeaderGetRange(req_hdr
);
518 if (request
->range
) {
519 request
->flags
.range
= 1;
520 clientStreamNode
*node
= (clientStreamNode
*)http
->client_stream
.tail
->data
;
521 /* XXX: This is suboptimal. We should give the stream the range set,
522 * and thereby let the top of the stream set the offset when the
523 * size becomes known. As it is, we will end up requesting from 0
524 * for evey -X range specification.
525 * RBC - this may be somewhat wrong. We should probably set the range
526 * iter up at this point.
528 node
->readBuffer
.offset
= request
->range
->lowestOffset(0);
529 http
->range_iter
.pos
= request
->range
->begin();
530 http
->range_iter
.valid
= true;
533 if (httpHeaderHas(req_hdr
, HDR_AUTHORIZATION
))
534 request
->flags
.auth
= 1;
535 if (request
->login
[0] != '\0')
536 request
->flags
.auth
= 1;
537 if (httpHeaderHas(req_hdr
, HDR_VIA
)) {
538 String s
= httpHeaderGetList(req_hdr
, HDR_VIA
);
540 * ThisCache cannot be a member of Via header, "1.0 ThisCache" can.
541 * Note ThisCache2 has a space prepended to the hostname so we don't
542 * accidentally match super-domains.
544 if (strListIsSubstr(&s
, ThisCache2
, ',')) {
545 debugObj(33, 1, "WARNING: Forwarding loop detected for:\n",
546 request
, (ObjPackMethod
) & httpRequestPack
);
547 request
->flags
.loopdetect
= 1;
550 fvdbCountVia(s
.buf());
554 #if USE_USERAGENT_LOG
555 if ((str
= httpHeaderGetStr(req_hdr
, HDR_USER_AGENT
)))
556 logUserAgent(fqdnFromAddr(http
->conn
? http
->conn
->log_addr
: no_addr
), str
);
559 if ((str
= httpHeaderGetStr(req_hdr
, HDR_REFERER
)))
560 logReferer(fqdnFromAddr(http
->conn
? http
->conn
->log_addr
: no_addr
), str
, http
->log_uri
);
563 if (httpHeaderHas(req_hdr
, HDR_X_FORWARDED_FOR
)) {
564 String s
= httpHeaderGetList(req_hdr
, HDR_X_FORWARDED_FOR
);
565 fvdbCountForw(s
.buf());
569 if (request
->method
== METHOD_TRACE
) {
570 request
->max_forwards
= httpHeaderGetInt(req_hdr
, HDR_MAX_FORWARDS
);
572 if (clientCachable(http
))
573 request
->flags
.cachable
= 1;
574 if (clientHierarchical(http
))
575 request
->flags
.hierarchical
= 1;
576 debug(85, 5) ("clientInterpretRequestHeaders: REQ_NOCACHE = %s\n",
577 request
->flags
.nocache
? "SET" : "NOT SET");
578 debug(85, 5) ("clientInterpretRequestHeaders: REQ_CACHABLE = %s\n",
579 request
->flags
.cachable
? "SET" : "NOT SET");
580 debug(85, 5) ("clientInterpretRequestHeaders: REQ_HIERARCHICAL = %s\n",
581 request
->flags
.hierarchical
? "SET" : "NOT SET");
585 clientRedirectDone(void *data
, char *result
)
587 clientRequestContext
*context
= (clientRequestContext
*)data
;
588 clientHttpRequest
*http
= context
->http
;
589 request_t
*new_request
= NULL
;
590 request_t
*old_request
= http
->request
;
591 debug(85, 5) ("clientRedirectDone: '%s' result=%s\n", http
->uri
,
592 result
? result
: "NULL");
593 assert(context
->redirect_state
== REDIRECT_PENDING
);
594 context
->redirect_state
= REDIRECT_DONE
;
596 http_status status
= (http_status
) atoi(result
);
597 if (status
== HTTP_MOVED_PERMANENTLY
598 || status
== HTTP_MOVED_TEMPORARILY
599 || status
== HTTP_SEE_OTHER
600 || status
== HTTP_TEMPORARY_REDIRECT
) {
602 if ((t
= strchr(result
, ':')) != NULL
) {
603 http
->redirect
.status
= status
;
604 http
->redirect
.location
= xstrdup(t
+ 1);
606 debug(85, 1) ("clientRedirectDone: bad input: %s\n", result
);
609 if (strcmp(result
, http
->uri
))
610 new_request
= urlParse(old_request
->method
, result
);
613 safe_free(http
->uri
);
614 http
->uri
= xstrdup(urlCanonical(new_request
));
615 new_request
->http_ver
= old_request
->http_ver
;
616 httpHeaderAppend(&new_request
->header
, &old_request
->header
);
617 new_request
->client_addr
= old_request
->client_addr
;
618 new_request
->my_addr
= old_request
->my_addr
;
619 new_request
->my_port
= old_request
->my_port
;
620 new_request
->flags
.redirected
= 1;
621 if (old_request
->auth_user_request
) {
622 new_request
->auth_user_request
= old_request
->auth_user_request
;
623 authenticateAuthUserRequestLock(new_request
->auth_user_request
);
625 if (old_request
->body_connection
) {
626 new_request
->body_connection
= old_request
->body_connection
;
627 old_request
->body_connection
= NULL
;
629 new_request
->content_length
= old_request
->content_length
;
630 new_request
->flags
.proxy_keepalive
= old_request
->flags
.proxy_keepalive
;
631 requestUnlink(old_request
);
632 http
->request
= requestLink(new_request
);
634 clientInterpretRequestHeaders(http
);
636 headersLog(0, 1, request
->method
, request
);
638 /* FIXME PIPELINE: This is innacurate during pipelining */
640 fd_note(http
->conn
->fd
, http
->uri
);
642 clientCheckNoCache(context
);
646 clientCheckNoCache(clientRequestContext
* context
)
648 clientHttpRequest
*http
= context
->http
;
649 if (Config
.accessList
.noCache
&& http
->request
->flags
.cachable
) {
650 context
->acl_checklist
=
651 clientAclChecklistCreate(Config
.accessList
.noCache
, http
);
652 aclNBCheck(context
->acl_checklist
, clientCheckNoCacheDone
, context
);
654 clientCheckNoCacheDone(http
->request
->flags
.cachable
, context
);
659 clientCheckNoCacheDone(int answer
, void *data
)
661 clientRequestContext
*context
= (clientRequestContext
*)data
;
662 clientHttpRequest
*http
= context
->http
;
663 http
->request
->flags
.cachable
= answer
;
664 context
->acl_checklist
= NULL
;
666 clientProcessRequest(http
);
670 * Identify requests that do not go through the store and client side stream
671 * and forward them to the appropriate location. All other requests, request
675 clientProcessRequest(clientHttpRequest
* http
)
677 request_t
*r
= http
->request
;
678 debug(85, 4) ("clientProcessRequest: %s '%s'\n",
679 RequestMethodStr
[r
->method
], http
->uri
);
680 if (r
->method
== METHOD_CONNECT
) {
681 http
->logType
= LOG_TCP_MISS
;
682 sslStart(http
, &http
->out
.size
, &http
->al
.http
.code
);
685 http
->logType
= LOG_TAG_NONE
;
687 debug(85, 4) ("clientProcessRequest: %s for '%s'\n",
688 log_tags
[http
->logType
], http
->uri
);
689 /* no one should have touched this */
690 assert(http
->out
.offset
== 0);
691 /* Use the Stream Luke */
692 clientStreamNode
*node
= (clientStreamNode
*)http
->client_stream
.tail
->data
;
693 clientStreamRead(node
, http
, node
->readBuffer
);