3 * $Id: client_side_request.cc,v 1.97 2007/12/14 23:11:46 amosjeffries 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
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.
38 * General logic of request processing:
40 * We run a series of tests to determine if access will be permitted, and to do
41 * any redirection. Then we call into the result clientStream to retrieve data.
42 * From that point on it's up to reply management.
46 #include "clientStream.h"
47 #include "client_side_request.h"
48 #include "AuthUserRequest.h"
49 #include "HttpRequest.h"
50 #include "ACLChecklist.h"
52 #include "client_side.h"
53 #include "client_side_reply.h"
55 #include "HttpReply.h"
56 #include "MemObject.h"
57 #include "ClientRequestContext.h"
58 #include "SquidTime.h"
62 #include "ICAP/ICAPModXact.h"
63 #include "ICAP/ICAPElements.h"
64 #include "ICAP/ICAPConfig.h"
65 static void icapAclCheckDoneWrapper(ICAPServiceRep::Pointer service
, void *data
);
66 extern ICAPConfig TheICAPConfig
;
70 #define comm_close comm_lingering_close
73 static const char *const crlf
= "\r\n";
75 CBDATA_CLASS_INIT(ClientRequestContext
);
78 ClientRequestContext::operator new (size_t size
)
80 assert (size
== sizeof(ClientRequestContext
));
81 CBDATA_INIT_TYPE(ClientRequestContext
);
82 ClientRequestContext
*result
= cbdataAlloc(ClientRequestContext
);
87 ClientRequestContext::operator delete (void *address
)
89 ClientRequestContext
*t
= static_cast<ClientRequestContext
*>(address
);
95 static void clientAccessCheckDoneWrapper(int, void *);
96 static int clientHierarchical(ClientHttpRequest
* http
);
97 static void clientInterpretRequestHeaders(ClientHttpRequest
* http
);
98 static RH clientRedirectDoneWrapper
;
99 static PF checkNoCacheDoneWrapper
;
100 extern "C" CSR clientGetMoreData
;
101 extern "C" CSS clientReplyStatus
;
102 extern "C" CSD clientReplyDetach
;
103 static void checkFailureRatio(err_type
, hier_code
);
105 ClientRequestContext::~ClientRequestContext()
108 * Release our "lock" on our parent, ClientHttpRequest, if we
113 cbdataReferenceDone(http
);
115 debugs(85,3, HERE
<< this << " ClientRequestContext destructed");
118 ClientRequestContext::ClientRequestContext(ClientHttpRequest
*anHttp
) : http(cbdataReference(anHttp
)), acl_checklist (NULL
), redirect_state (REDIRECT_NONE
)
120 http_access_done
= false;
121 redirect_done
= false;
122 no_cache_done
= false;
123 interpreted_req_hdrs
= false;
124 debugs(85,3, HERE
<< this << " ClientRequestContext constructed");
127 CBDATA_CLASS_INIT(ClientHttpRequest
);
130 ClientHttpRequest::operator new (size_t size
)
132 assert (size
== sizeof (ClientHttpRequest
));
133 CBDATA_INIT_TYPE(ClientHttpRequest
);
134 ClientHttpRequest
*result
= cbdataAlloc(ClientHttpRequest
);
139 ClientHttpRequest::operator delete (void *address
)
141 ClientHttpRequest
*t
= static_cast<ClientHttpRequest
*>(address
);
145 ClientHttpRequest::ClientHttpRequest(ConnStateData::Pointer aConn
) : loggingEntry_(NULL
)
147 start
= current_time
;
149 dlinkAdd(this, &active
, &ClientActiveRequests
);
151 request_satisfaction_mode
= false;
156 * returns true if client specified that the object must come from the cache
157 * without contacting origin server
160 ClientHttpRequest::onlyIfCached()const
163 return request
->cache_control
&&
164 EBIT_TEST(request
->cache_control
->mask
, CC_ONLY_IF_CACHED
);
168 * This function is designed to serve a fairly specific purpose.
169 * Occasionally our vBNS-connected caches can talk to each other, but not
170 * the rest of the world. Here we try to detect frequent failures which
171 * make the cache unusable (e.g. DNS lookup and connect() failures). If
172 * the failure:success ratio goes above 1.0 then we go into "hit only"
173 * mode where we only return UDP_HIT or UDP_MISS_NOFETCH. Neighbors
174 * will only fetch HITs from us if they are using the ICP protocol. We
175 * stay in this mode for 5 minutes.
177 * Duane W., Sept 16, 1996
180 #define FAILURE_MODE_TIME 300
183 checkFailureRatio(err_type etype
, hier_code hcode
)
185 static double magic_factor
= 100.0;
189 if (hcode
== HIER_NONE
)
192 n_good
= magic_factor
/ (1.0 + request_failure_ratio
);
194 n_bad
= magic_factor
- n_good
;
200 case ERR_CONNECT_FAIL
:
210 request_failure_ratio
= n_bad
/ n_good
;
212 if (hit_only_mode_until
> squid_curtime
)
215 if (request_failure_ratio
< 1.0)
218 debugs(33, 0, "Failure Ratio at "<< std::setw(4)<<
219 std::setprecision(3) << request_failure_ratio
);
221 debugs(33, 0, "Going into hit-only-mode for " <<
222 FAILURE_MODE_TIME
/ 60 << " minutes...");
224 hit_only_mode_until
= squid_curtime
+ FAILURE_MODE_TIME
;
226 request_failure_ratio
= 0.8; /* reset to something less than 1.0 */
229 ClientHttpRequest::~ClientHttpRequest()
231 debugs(33, 3, "httpRequestFree: " << uri
);
232 PROF_start(httpRequestFree
);
234 // Even though freeResources() below may destroy the request,
235 // we no longer set request->body_pipe to NULL here
236 // because we did not initiate that pipe (ConnStateData did)
238 /* the ICP check here was erroneous
239 * - StoreEntry::releaseRequest was always called if entry was valid
241 assert(logType
< LOG_TYPE_MAX
);
248 checkFailureRatio(request
->errType
, al
.hier
.code
);
253 announceInitiatorAbort(icapHeadSource
);
255 if (icapBodySource
!= NULL
)
256 stopConsumingFrom(icapBodySource
);
260 delete calloutContext
;
262 /* moving to the next connection is handled by the context free */
263 dlinkDelete(&active
, &ClientActiveRequests
);
265 PROF_stop(httpRequestFree
);
268 /* Create a request and kick it off */
270 * TODO: Pass in the buffers to be used in the inital Read request, as they are
271 * determined by the user
273 int /* returns nonzero on failure */
274 clientBeginRequest(method_t method
, char const *url
, CSCB
* streamcallback
,
275 CSD
* streamdetach
, ClientStreamData streamdata
, HttpHeader
const *header
,
276 char *tailbuf
, size_t taillen
)
279 HttpVersion
http_ver (1, 0);
280 ClientHttpRequest
*http
= new ClientHttpRequest(NULL
);
281 HttpRequest
*request
;
282 StoreIOBuffer tempBuffer
;
283 http
->start
= current_time
;
284 /* this is only used to adjust the connection offset in client_side.c */
286 tempBuffer
.length
= taillen
;
287 tempBuffer
.data
= tailbuf
;
288 /* client stream setup */
289 clientStreamInit(&http
->client_stream
, clientGetMoreData
, clientReplyDetach
,
290 clientReplyStatus
, new clientReplyContext(http
), streamcallback
,
291 streamdetach
, streamdata
, tempBuffer
);
292 /* make it visible in the 'current acctive requests list' */
294 /* internal requests only makes sense in an
295 * accelerator today. TODO: accept flags ? */
296 http
->flags
.accel
= 1;
297 /* allow size for url rewriting */
298 url_sz
= strlen(url
) + Config
.appendDomainLen
+ 5;
299 http
->uri
= (char *)xcalloc(url_sz
, 1);
300 strcpy(http
->uri
, url
);
302 if ((request
= HttpRequest::CreateFromUrlAndMethod(http
->uri
, method
)) == NULL
) {
303 debugs(85, 5, "Invalid URL: " << http
->uri
);
308 * now update the headers in request with our supplied headers. urLParse
309 * should return a blank header set, but we use Update to be sure of
313 request
->header
.update(header
, NULL
);
315 http
->log_uri
= xstrdup(urlCanonicalClean(request
));
317 /* http struct now ready */
320 * build new header list *? TODO
322 request
->flags
.accelerated
= http
->flags
.accel
;
324 request
->flags
.internalclient
= 1;
326 /* this is an internally created
327 * request, not subject to acceleration
328 * target overrides */
330 * FIXME? Do we want to detect and handle internal requests of internal
334 /* Internally created requests cannot have bodies today */
335 request
->content_length
= 0;
337 request
->client_addr
.SetNoAddr();
339 request
->my_addr
.SetNoAddr(); /* undefined for internal requests */
341 request
->my_addr
.SetPort(0);
343 request
->http_ver
= http_ver
;
345 http
->request
= HTTPMSGLOCK(request
);
347 /* optional - skip the access check ? */
348 http
->calloutContext
= new ClientRequestContext(http
);
350 http
->calloutContext
->http_access_done
= false;
352 http
->calloutContext
->redirect_done
= true;
354 http
->calloutContext
->no_cache_done
= true;
362 ClientRequestContext::httpStateIsValid()
364 ClientHttpRequest
*http_
= http
;
366 if (cbdataReferenceValid(http_
))
371 cbdataReferenceDone(http_
);
376 /* This is the entry point for external users of the client_side routines */
378 ClientRequestContext::clientAccessCheck()
381 clientAclChecklistCreate(Config
.accessList
.http
, http
);
382 acl_checklist
->nonBlockingCheck(clientAccessCheckDoneWrapper
, this);
386 clientAccessCheckDoneWrapper(int answer
, void *data
)
388 ClientRequestContext
*calloutContext
= (ClientRequestContext
*) data
;
390 if (!calloutContext
->httpStateIsValid())
393 calloutContext
->clientAccessCheckDone(answer
);
397 ClientRequestContext::clientAccessCheckDone(int answer
)
399 acl_checklist
= NULL
;
402 debugs(85, 2, "The request " <<
403 RequestMethodStr
[http
->request
->method
] << " " <<
404 http
->uri
<< " is " <<
405 (answer
== ACCESS_ALLOWED
? "ALLOWED" : "DENIED") <<
406 ", because it matched '" <<
407 (AclMatchedName
? AclMatchedName
: "NO ACL's") << "'" );
408 char const *proxy_auth_msg
= "<null>";
410 if (http
->getConn() != NULL
&& http
->getConn()->auth_user_request
!= NULL
)
411 proxy_auth_msg
= http
->getConn()->auth_user_request
->denyMessage("<null>");
412 else if (http
->request
->auth_user_request
!= NULL
)
413 proxy_auth_msg
= http
->request
->auth_user_request
->denyMessage("<null>");
415 if (answer
!= ACCESS_ALLOWED
) {
417 int require_auth
= (answer
== ACCESS_REQ_PROXY_AUTH
|| aclIsProxyAuth(AclMatchedName
));
418 debugs(85, 5, "Access Denied: " << http
->uri
);
419 debugs(85, 5, "AclMatchedName = " << (AclMatchedName
? AclMatchedName
: "<null>"));
422 debugs(33, 5, "Proxy Auth Message = " << (proxy_auth_msg
? proxy_auth_msg
: "<null>"));
425 * NOTE: get page_id here, based on AclMatchedName because if
426 * USE_DELAY_POOLS is enabled, then AclMatchedName gets clobbered in
427 * the clientCreateStoreEntry() call just below. Pedro Ribeiro
430 page_id
= aclGetDenyInfoPage(&Config
.denyInfoList
, AclMatchedName
, answer
!= ACCESS_REQ_PROXY_AUTH
);
432 http
->logType
= LOG_TCP_DENIED
;
435 if (!http
->flags
.accel
) {
436 /* Proxy authorisation needed */
437 status
= HTTP_PROXY_AUTHENTICATION_REQUIRED
;
439 /* WWW authorisation needed */
440 status
= HTTP_UNAUTHORIZED
;
443 if (page_id
== ERR_NONE
)
444 page_id
= ERR_CACHE_ACCESS_DENIED
;
446 status
= HTTP_FORBIDDEN
;
448 if (page_id
== ERR_NONE
)
449 page_id
= ERR_ACCESS_DENIED
;
452 clientStreamNode
*node
= (clientStreamNode
*)http
->client_stream
.tail
->prev
->data
;
453 clientReplyContext
*repContext
= dynamic_cast<clientReplyContext
*>(node
->data
.getRaw());
455 IPAddress tmpnoaddr
; tmpnoaddr
.SetNoAddr();
456 repContext
->setReplyToError(page_id
, status
,
457 http
->request
->method
, NULL
,
458 http
->getConn() != NULL
? http
->getConn()->peer
: tmpnoaddr
,
461 http
->getConn() != NULL
&& http
->getConn()->auth_user_request
?
462 http
->getConn()->auth_user_request
: http
->request
->auth_user_request
);
464 node
= (clientStreamNode
*)http
->client_stream
.tail
->data
;
465 clientStreamRead(node
, http
, node
->readBuffer
);
469 /* ACCESS_ALLOWED continues here ... */
470 safe_free(http
->uri
);
472 http
->uri
= xstrdup(urlCanonical(http
->request
));
479 ClientRequestContext::icapAccessCheck()
481 ICAPAccessCheck
*icap_access_check
;
483 icap_access_check
= new ICAPAccessCheck(ICAP::methodReqmod
, ICAP::pointPreCache
, http
->request
, NULL
, icapAclCheckDoneWrapper
, this);
485 if (icap_access_check
!= NULL
) {
486 icap_access_check
->check();
494 icapAclCheckDoneWrapper(ICAPServiceRep::Pointer service
, void *data
)
496 ClientRequestContext
*calloutContext
= (ClientRequestContext
*)data
;
498 if (!calloutContext
->httpStateIsValid())
501 calloutContext
->icapAclCheckDone(service
);
505 ClientRequestContext::icapAclCheckDone(ICAPServiceRep::Pointer service
)
507 debugs(93,3,HERE
<< this << " icapAclCheckDone called");
510 if (http
->startIcap(service
))
513 if (!service
|| service
->bypass
) {
514 // handle ICAP start failure when no service was selected
515 // or where the selected service was optional
520 // handle start failure for an essential ICAP service
521 http
->handleIcapFailure();
527 clientRedirectAccessCheckDone(int answer
, void *data
)
529 ClientRequestContext
*context
= (ClientRequestContext
*)data
;
530 ClientHttpRequest
*http
= context
->http
;
531 context
->acl_checklist
= NULL
;
533 if (answer
== ACCESS_ALLOWED
)
534 redirectStart(http
, clientRedirectDoneWrapper
, context
);
536 context
->clientRedirectDone(NULL
);
540 ClientRequestContext::clientRedirectStart()
542 debugs(33, 5, "clientRedirectStart: '" << http
->uri
<< "'");
544 if (Config
.accessList
.redirector
) {
545 acl_checklist
= clientAclChecklistCreate(Config
.accessList
.redirector
, http
);
546 acl_checklist
->nonBlockingCheck(clientRedirectAccessCheckDone
, this);
548 redirectStart(http
, clientRedirectDoneWrapper
, this);
552 clientHierarchical(ClientHttpRequest
* http
)
554 const char *url
= http
->uri
;
555 HttpRequest
*request
= http
->request
;
556 method_t method
= request
->method
;
557 const wordlist
*p
= NULL
;
560 * IMS needs a private key, so we can use the hierarchy for IMS only if our
561 * neighbors support private keys
564 if (request
->flags
.ims
&& !neighbors_do_private_keys
)
568 * This is incorrect: authenticating requests can be sent via a hierarchy
569 * (they can even be cached if the correct headers are set on the reply)
571 if (request
->flags
.auth
)
574 if (method
== METHOD_TRACE
)
577 if (method
!= METHOD_GET
)
580 /* scan hierarchy_stoplist */
581 for (p
= Config
.hierarchy_stoplist
; p
; p
= p
->next
)
582 if (strstr(url
, p
->key
))
585 if (request
->flags
.loopdetect
)
588 if (request
->protocol
== PROTO_HTTP
)
589 return httpCachable(method
);
591 if (request
->protocol
== PROTO_GOPHER
)
592 return gopherCachable(request
);
594 if (request
->protocol
== PROTO_CACHEOBJ
)
602 clientInterpretRequestHeaders(ClientHttpRequest
* http
)
604 HttpRequest
*request
= http
->request
;
605 HttpHeader
*req_hdr
= &request
->header
;
607 #if !(USE_SQUID_ESI) || defined(USE_USERAGENT_LOG) || defined(USE_REFERER_LOG)
612 request
->imslen
= -1;
613 request
->ims
= req_hdr
->getTime(HDR_IF_MODIFIED_SINCE
);
615 if (request
->ims
> 0)
616 request
->flags
.ims
= 1;
620 * We ignore Cache-Control as per the Edge Architecture Section 3. See
621 * www.esi.org for more information.
625 if (req_hdr
->has(HDR_PRAGMA
)) {
626 String s
= req_hdr
->getList(HDR_PRAGMA
);
628 if (strListIsMember(&s
, "no-cache", ','))
634 if (request
->cache_control
)
635 if (EBIT_TEST(request
->cache_control
->mask
, CC_NO_CACHE
))
639 * Work around for supporting the Reload button in IE browsers when Squid
640 * is used as an accelerator or transparent proxy, by turning accelerated
641 * IMS request to no-cache requests. Now knows about IE 5.5 fix (is
642 * actually only fixed in SP1, but we can't tell whether we are talking to
643 * SP1 or not so all 5.5 versions are treated 'normally').
645 if (Config
.onoff
.ie_refresh
) {
646 if (http
->flags
.accel
&& request
->flags
.ims
) {
647 if ((str
= req_hdr
->getStr(HDR_USER_AGENT
))) {
648 if (strstr(str
, "MSIE 5.01") != NULL
)
650 else if (strstr(str
, "MSIE 5.0") != NULL
)
652 else if (strstr(str
, "MSIE 4.") != NULL
)
654 else if (strstr(str
, "MSIE 3.") != NULL
)
664 if (Config
.onoff
.reload_into_ims
)
665 request
->flags
.nocache_hack
= 1;
666 else if (refresh_nocache_hack
)
667 request
->flags
.nocache_hack
= 1;
671 request
->flags
.nocache
= 1;
674 /* ignore range header in non-GETs or non-HEADs */
675 if (request
->method
== METHOD_GET
|| request
->method
== METHOD_HEAD
) {
676 request
->range
= req_hdr
->getRange();
678 if (request
->range
) {
679 request
->flags
.range
= 1;
680 clientStreamNode
*node
= (clientStreamNode
*)http
->client_stream
.tail
->data
;
681 /* XXX: This is suboptimal. We should give the stream the range set,
682 * and thereby let the top of the stream set the offset when the
683 * size becomes known. As it is, we will end up requesting from 0
684 * for evey -X range specification.
685 * RBC - this may be somewhat wrong. We should probably set the range
686 * iter up at this point.
688 node
->readBuffer
.offset
= request
->range
->lowestOffset(0);
689 http
->range_iter
.pos
= request
->range
->begin();
690 http
->range_iter
.valid
= true;
694 /* Only HEAD and GET requests permit a Range or Request-Range header.
695 * If these headers appear on any other type of request, delete them now.
698 req_hdr
->delById(HDR_RANGE
);
699 req_hdr
->delById(HDR_REQUEST_RANGE
);
700 request
->range
= NULL
;
703 if (req_hdr
->has(HDR_AUTHORIZATION
))
704 request
->flags
.auth
= 1;
706 if (request
->login
[0] != '\0')
707 request
->flags
.auth
= 1;
709 if (req_hdr
->has(HDR_VIA
)) {
710 String s
= req_hdr
->getList(HDR_VIA
);
712 * ThisCache cannot be a member of Via header, "1.0 ThisCache" can.
713 * Note ThisCache2 has a space prepended to the hostname so we don't
714 * accidentally match super-domains.
717 if (strListIsSubstr(&s
, ThisCache2
, ',')) {
718 debugObj(33, 1, "WARNING: Forwarding loop detected for:\n",
719 request
, (ObjPackMethod
) & httpRequestPack
);
720 request
->flags
.loopdetect
= 1;
724 fvdbCountVia(s
.buf());
731 #if USE_USERAGENT_LOG
732 if ((str
= req_hdr
->getStr(HDR_USER_AGENT
)))
733 logUserAgent(fqdnFromAddr(http
->getConn() != NULL
? http
->getConn()->log_addr
: no_addr
), str
);
738 if ((str
= req_hdr
->getStr(HDR_REFERER
)))
739 logReferer(fqdnFromAddr(http
->getConn() != NULL
? http
->getConn()->log_addr
: no_addr
), str
, http
->log_uri
);
744 if (req_hdr
->has(HDR_X_FORWARDED_FOR
)) {
745 String s
= req_hdr
->getList(HDR_X_FORWARDED_FOR
);
746 fvdbCountForw(s
.buf());
751 if (request
->method
== METHOD_TRACE
) {
752 request
->max_forwards
= req_hdr
->getInt(HDR_MAX_FORWARDS
);
755 request
->flags
.cachable
= http
->request
->cacheable();
757 if (clientHierarchical(http
))
758 request
->flags
.hierarchical
= 1;
760 debugs(85, 5, "clientInterpretRequestHeaders: REQ_NOCACHE = " <<
761 (request
->flags
.nocache
? "SET" : "NOT SET"));
762 debugs(85, 5, "clientInterpretRequestHeaders: REQ_CACHABLE = " <<
763 (request
->flags
.cachable
? "SET" : "NOT SET"));
764 debugs(85, 5, "clientInterpretRequestHeaders: REQ_HIERARCHICAL = " <<
765 (request
->flags
.hierarchical
? "SET" : "NOT SET"));
770 clientRedirectDoneWrapper(void *data
, char *result
)
772 ClientRequestContext
*calloutContext
= (ClientRequestContext
*)data
;
774 if (!calloutContext
->httpStateIsValid())
777 calloutContext
->clientRedirectDone(result
);
781 ClientRequestContext::clientRedirectDone(char *result
)
783 HttpRequest
*new_request
= NULL
;
784 HttpRequest
*old_request
= http
->request
;
785 debugs(85, 5, "clientRedirectDone: '" << http
->uri
<< "' result=" << (result
? result
: "NULL"));
786 assert(redirect_state
== REDIRECT_PENDING
);
787 redirect_state
= REDIRECT_DONE
;
790 http_status status
= (http_status
) atoi(result
);
792 if (status
== HTTP_MOVED_PERMANENTLY
793 || status
== HTTP_MOVED_TEMPORARILY
794 || status
== HTTP_SEE_OTHER
795 || status
== HTTP_TEMPORARY_REDIRECT
) {
798 if ((t
= strchr(result
, ':')) != NULL
) {
799 http
->redirect
.status
= status
;
800 http
->redirect
.location
= xstrdup(t
+ 1);
802 debugs(85, 1, "clientRedirectDone: bad input: " << result
);
804 } else if (strcmp(result
, http
->uri
))
805 new_request
= HttpRequest::CreateFromUrlAndMethod(result
, old_request
->method
);
809 safe_free(http
->uri
);
810 http
->uri
= xstrdup(urlCanonical(new_request
));
811 new_request
->http_ver
= old_request
->http_ver
;
812 new_request
->header
.append(&old_request
->header
);
813 new_request
->client_addr
= old_request
->client_addr
;
814 new_request
->my_addr
= old_request
->my_addr
;
815 new_request
->flags
= old_request
->flags
;
816 new_request
->flags
.redirected
= 1;
818 if (old_request
->auth_user_request
) {
819 new_request
->auth_user_request
= old_request
->auth_user_request
;
820 AUTHUSERREQUESTLOCK(new_request
->auth_user_request
, "new request");
823 if (old_request
->body_pipe
!= NULL
) {
824 new_request
->body_pipe
= old_request
->body_pipe
;
825 old_request
->body_pipe
= NULL
;
826 debugs(0,0,HERE
<< "redirecting body_pipe " << new_request
->body_pipe
<< " from request " << old_request
<< " to " << new_request
);
829 new_request
->content_length
= old_request
->content_length
;
830 new_request
->extacl_user
= old_request
->extacl_user
;
831 new_request
->extacl_passwd
= old_request
->extacl_passwd
;
832 new_request
->flags
.proxy_keepalive
= old_request
->flags
.proxy_keepalive
;
833 HTTPMSGUNLOCK(old_request
);
834 http
->request
= HTTPMSGLOCK(new_request
);
837 /* FIXME PIPELINE: This is innacurate during pipelining */
839 if (http
->getConn() != NULL
)
840 fd_note(http
->getConn()->fd
, http
->uri
);
848 ClientRequestContext::checkNoCache()
850 acl_checklist
= clientAclChecklistCreate(Config
.accessList
.noCache
, http
);
851 acl_checklist
->nonBlockingCheck(checkNoCacheDoneWrapper
, this);
855 checkNoCacheDoneWrapper(int answer
, void *data
)
857 ClientRequestContext
*calloutContext
= (ClientRequestContext
*) data
;
859 if (!calloutContext
->httpStateIsValid())
862 calloutContext
->checkNoCacheDone(answer
);
866 ClientRequestContext::checkNoCacheDone(int answer
)
868 acl_checklist
= NULL
;
869 http
->request
->flags
.cachable
= answer
;
874 * Identify requests that do not go through the store and client side stream
875 * and forward them to the appropriate location. All other requests, request
879 ClientHttpRequest::processRequest()
881 debugs(85, 4, "clientProcessRequest: " << RequestMethodStr
[request
->method
] << " '" << uri
<< "'");
883 if (request
->method
== METHOD_CONNECT
&& !redirect
.status
) {
884 logType
= LOG_TCP_MISS
;
885 tunnelStart(this, &out
.size
, &al
.http
.code
);
893 ClientHttpRequest::httpStart()
895 PROF_start(httpStart
);
896 logType
= LOG_TAG_NONE
;
897 debugs(85, 4, "ClientHttpRequest::httpStart: " << log_tags
[logType
] << " for '" << uri
<< "'");
899 /* no one should have touched this */
900 assert(out
.offset
== 0);
901 /* Use the Stream Luke */
902 clientStreamNode
*node
= (clientStreamNode
*)client_stream
.tail
->data
;
903 clientStreamRead(node
, this, node
->readBuffer
);
904 PROF_stop(httpStart
);
908 ClientHttpRequest::gotEnough() const
910 /** TODO: should be querying the stream. */
911 int64_t contentLength
=
912 memObject()->getReply()->bodySize(request
->method
);
913 assert(contentLength
>= 0);
915 if (out
.offset
< contentLength
)
922 ClientHttpRequest::maxReplyBodySize(int64_t clen
)
924 maxReplyBodySize_
= clen
;
928 ClientHttpRequest::maxReplyBodySize() const
930 return maxReplyBodySize_
;
934 ClientHttpRequest::isReplyBodyTooLarge(int64_t clen
) const
936 if (0 == maxReplyBodySize())
937 return 0; /* disabled */
940 return 0; /* unknown */
942 return clen
> maxReplyBodySize();
946 ClientHttpRequest::storeEntry(StoreEntry
*newEntry
)
952 ClientHttpRequest::loggingEntry(StoreEntry
*newEntry
)
955 loggingEntry_
->unlock();
957 loggingEntry_
= newEntry
;
960 loggingEntry_
->lock()
966 * doCallouts() - This function controls the order of "callout"
967 * executions, including non-blocking access control checks, the
968 * redirector, and ICAP. Previously, these callouts were chained
969 * together such that "clientAccessCheckDone()" would call
970 * "clientRedirectStart()" and so on.
972 * The ClientRequestContext (aka calloutContext) class holds certain
973 * state data for the callout/callback operations. Previously
974 * ClientHttpRequest would sort of hand off control to ClientRequestContext
975 * for a short time. ClientRequestContext would then delete itself
976 * and pass control back to ClientHttpRequest when all callouts
979 * This caused some problems for ICAP because we want to make the
980 * ICAP callout after checking ACLs, but before checking the no_cache
981 * list. We can't stuff the ICAP state into the ClientRequestContext
982 * class because we still need the ICAP state after ClientRequestContext
985 * Note that ClientRequestContext is created before the first call
988 * If one of the callouts notices that ClientHttpRequest is no
989 * longer valid, it should call cbdataReferenceDone() so that
990 * ClientHttpRequest's reference count goes to zero and it will get
991 * deleted. ClientHttpRequest will then delete ClientRequestContext.
993 * Note that we set the _done flags here before actually starting
994 * the callout. This is strictly for convenience.
997 extern int aclMapTOS (acl_tos
* head
, ACLChecklist
* ch
);
1000 ClientHttpRequest::doCallouts()
1002 assert(calloutContext
);
1004 if (!calloutContext
->http_access_done
) {
1005 debugs(83, 3, HERE
<< "Doing calloutContext->clientAccessCheck()");
1006 calloutContext
->http_access_done
= true;
1007 calloutContext
->clientAccessCheck();
1012 if (TheICAPConfig
.onoff
&& !calloutContext
->icap_acl_check_done
) {
1013 debugs(83, 3, HERE
<< "Doing calloutContext->icapAccessCheck()");
1014 calloutContext
->icap_acl_check_done
= true;
1015 calloutContext
->icapAccessCheck();
1021 if (!calloutContext
->redirect_done
) {
1022 calloutContext
->redirect_done
= true;
1023 assert(calloutContext
->redirect_state
== REDIRECT_NONE
);
1025 if (Config
.Program
.redirect
) {
1026 debugs(83, 3, HERE
<< "Doing calloutContext->clientRedirectStart()");
1027 calloutContext
->redirect_state
= REDIRECT_PENDING
;
1028 calloutContext
->clientRedirectStart();
1033 if (!calloutContext
->interpreted_req_hdrs
) {
1034 debugs(83, 3, HERE
<< "Doing clientInterpretRequestHeaders()");
1035 calloutContext
->interpreted_req_hdrs
= 1;
1036 clientInterpretRequestHeaders(this);
1039 if (!calloutContext
->no_cache_done
) {
1040 calloutContext
->no_cache_done
= true;
1042 if (Config
.accessList
.noCache
&& request
->flags
.cachable
) {
1043 debugs(83, 3, HERE
<< "Doing calloutContext->checkNoCache()");
1044 calloutContext
->checkNoCache();
1049 if (!calloutContext
->clientside_tos_done
) {
1050 calloutContext
->clientside_tos_done
= true;
1051 if (getConn() != NULL
) {
1053 ch
.src_addr
= request
->client_addr
;
1054 ch
.my_addr
= request
->my_addr
;
1055 ch
.request
= HTTPMSGLOCK(request
);
1056 int tos
= aclMapTOS(Config
.accessList
.clientside_tos
, &ch
);
1058 comm_set_tos(getConn()->fd
, tos
);
1062 cbdataReferenceDone(calloutContext
->http
);
1063 delete calloutContext
;
1064 calloutContext
= NULL
;
1067 headersLog(0, 1, request
->method
, request
);
1070 debugs(83, 3, HERE
<< "calling processRequest()");
1074 #ifndef _USE_INLINE_
1075 #include "client_side_request.cci"
1080 * Initiate an ICAP transaction. Return false on errors.
1081 * The caller must handle errors.
1084 ClientHttpRequest::startIcap(ICAPServiceRep::Pointer service
)
1086 debugs(85, 3, HERE
<< this << " ClientHttpRequest::startIcap() called");
1088 debugs(85, 3, "ClientHttpRequest::startIcap fails: lack of service");
1091 if (service
->broken()) {
1092 debugs(85, 3, "ClientHttpRequest::startIcap fails: broken service");
1096 assert(!icapHeadSource
);
1097 assert(!icapBodySource
);
1098 icapHeadSource
= initiateIcap(
1099 new ICAPModXactLauncher(this, request
, NULL
, service
));
1104 ClientHttpRequest::noteIcapAnswer(HttpMsg
*msg
)
1106 assert(cbdataReferenceValid(this)); // indicates bug
1109 if (HttpRequest
*new_req
= dynamic_cast<HttpRequest
*>(msg
)) {
1111 * Replace the old request with the new request.
1113 HTTPMSGUNLOCK(request
);
1114 request
= HTTPMSGLOCK(new_req
);
1116 * Store the new URI for logging
1119 uri
= xstrdup(urlCanonical(request
));
1120 setLogUri(this, urlCanonicalClean(request
));
1121 assert(request
->method
);
1122 } else if (HttpReply
*new_rep
= dynamic_cast<HttpReply
*>(msg
)) {
1123 debugs(85,3,HERE
<< "REQMOD reply is HTTP reply");
1125 // subscribe to receive reply body
1126 if (new_rep
->body_pipe
!= NULL
) {
1127 icapBodySource
= new_rep
->body_pipe
;
1128 assert(icapBodySource
->setConsumerIfNotLate(this));
1131 clientStreamNode
*node
= (clientStreamNode
*)client_stream
.tail
->prev
->data
;
1132 clientReplyContext
*repContext
= dynamic_cast<clientReplyContext
*>(node
->data
.getRaw());
1133 repContext
->createStoreEntry(request
->method
, request
->flags
);
1135 EBIT_CLR(storeEntry()->flags
, ENTRY_FWD_HDR_WAIT
);
1136 request_satisfaction_mode
= true;
1137 request_satisfaction_offset
= 0;
1138 storeEntry()->replaceHttpReply(new_rep
);
1140 if (!icapBodySource
) // no body
1141 storeEntry()->complete();
1142 clientGetMoreData(node
, this);
1145 // we are done with getting headers (but may be receiving body)
1146 clearIcap(icapHeadSource
);
1148 if (!request_satisfaction_mode
)
1153 ClientHttpRequest::noteIcapQueryAbort(bool final
)
1155 clearIcap(icapHeadSource
);
1156 assert(!icapBodySource
);
1157 handleIcapFailure(!final
);
1161 ClientHttpRequest::noteMoreBodyDataAvailable(BodyPipe
&)
1163 assert(request_satisfaction_mode
);
1164 assert(icapBodySource
!= NULL
);
1166 if (const size_t contentSize
= icapBodySource
->buf().contentSize()) {
1167 BodyPipeCheckout
bpc(*icapBodySource
);
1168 const StoreIOBuffer
ioBuf(&bpc
.buf
, request_satisfaction_offset
);
1169 storeEntry()->write(ioBuf
);
1170 // assume can write everything
1171 request_satisfaction_offset
+= contentSize
;
1172 bpc
.buf
.consume(contentSize
);
1176 if (icapBodySource
->exhausted())
1177 endRequestSatisfaction();
1178 // else wait for more body data
1182 ClientHttpRequest::noteBodyProductionEnded(BodyPipe
&)
1184 assert(!icapHeadSource
);
1185 if (icapBodySource
!= NULL
) { // did not end request satisfaction yet
1186 // We do not expect more because noteMoreBodyDataAvailable always
1187 // consumes everything. We do not even have a mechanism to consume
1188 // leftovers after noteMoreBodyDataAvailable notifications seize.
1189 assert(icapBodySource
->exhausted());
1190 endRequestSatisfaction();
1195 ClientHttpRequest::endRequestSatisfaction() {
1196 debugs(85,4, HERE
<< this << " ends request satisfaction");
1197 assert(request_satisfaction_mode
);
1198 stopConsumingFrom(icapBodySource
);
1200 // TODO: anything else needed to end store entry formation correctly?
1201 storeEntry()->complete();
1205 ClientHttpRequest::noteBodyProducerAborted(BodyPipe
&)
1207 assert(!icapHeadSource
);
1208 stopConsumingFrom(icapBodySource
);
1209 handleIcapFailure();
1213 ClientHttpRequest::handleIcapFailure(bool bypassable
)
1215 debugs(85,3, HERE
<< "handleIcapFailure(" << bypassable
<< ")");
1217 const bool usedStore
= storeEntry() && !storeEntry()->isEmpty();
1218 const bool usedPipe
= request
->body_pipe
!= NULL
&&
1219 request
->body_pipe
->consumedSize() > 0;
1221 if (bypassable
&& !usedStore
&& !usedPipe
) {
1222 debugs(85,3, HERE
<< "ICAP REQMOD callout failed, bypassing: " << calloutContext
);
1228 debugs(85,3, HERE
<< "ICAP REQMOD callout failed, responding with error");
1230 clientStreamNode
*node
= (clientStreamNode
*)client_stream
.tail
->prev
->data
;
1231 clientReplyContext
*repContext
= dynamic_cast<clientReplyContext
*>(node
->data
.getRaw());
1234 // The original author of the code also wanted to pass an errno to
1235 // setReplyToError, but it seems unlikely that the errno reflects the
1236 // true cause of the error at this point, so I did not pass it.
1237 ConnStateData::Pointer c
= getConn();
1238 repContext
->setReplyToError(ERR_ICAP_FAILURE
, HTTP_INTERNAL_SERVER_ERROR
,
1239 request
->method
, NULL
,
1240 (c
!= NULL
? &c
->peer
.sin_addr
: &no_addr
), request
, NULL
,
1241 (c
!= NULL
&& c
->auth_user_request
?
1242 c
->auth_user_request
: request
->auth_user_request
));
1244 node
= (clientStreamNode
*)client_stream
.tail
->data
;
1245 clientStreamRead(node
, this, node
->readBuffer
);