2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 87 Client-side Stream routines. */
12 #include "client_side_request.h"
13 #include "clientStream.h"
14 #include "http/Stream.h"
15 #include "HttpReply.h"
16 #include "HttpRequest.h"
19 \defgroup ClientStreamInternal Client Streams Internals
20 \ingroup ClientStreamAPI
22 * A client Stream is a uni directional pipe, with the usual non-blocking
23 * asynchronous approach present elsewhere in squid.
26 * Each pipe node has a data push function, and a data request function.
27 * This limits flexibility - the data flow is no longer assembled at each
31 * An alternative approach is to pass each node in the pipe the call-
32 * back to use on each IO call. This allows the callbacks to be changed
33 * very easily by a participating node, but requires more maintenance
34 * in each node (store the callback to the most recent IO request in
35 * the nodes context.) Such an approach also prevents dynamically
36 * changing the pipeline from outside without an additional interface
37 * method to extract the callback and context from the next node.
40 * One important characteristic of the stream is that the readfunc
41 * on the terminating node, and the callback on the first node
42 * will be NULL, and never used.
44 \section QuickNotes Quick Notes
46 * Each node including the HEAD of the clientStream has a cbdataReference
47 * held by the stream. Freeing the stream then removes that reference
48 * and delete's every node.
49 * Any node with other References, and all nodes downstream will only
50 * free when those references are released.
51 * Stream nodes MAY hold references to the data member of the node.
54 * Specifically - on creation no reference is made.
55 * If you pass a data variable to a node, give it an initial reference.
56 * If the data member is non-null on FREE, cbdataFree WILL be called.
57 * This you must never call cbdataFree on your own context without
58 * explicitly setting the stream node data member to NULL and
59 * cbdataReferenceDone'ing it.
62 * No data member may hold a reference to it's stream node.
63 * The stream guarantees that DETACH will be called before
64 * freeing the node, allowing data members to cleanup.
67 * If a node's data holds a reference to something that needs to
68 * free the stream a circular reference list will occur.
69 * This results no data being freed until that reference is removed.
70 * One way to accomplish thisObject is to explicitly remove the
71 * data from your own node before freeing the stream.
74 mycontext = thisObject->data;
75 thisObject->data = NULL;
76 delete thisObject->head;
81 * TODO: rather than each node undeleting the next, have a clientStreamDelete that walks the list.
84 CBDATA_CLASS_INIT(clientStreamNode
);
86 clientStreamNode::clientStreamNode(CSR
* aReadfunc
, CSCB
* aCallback
, CSD
* aDetach
, CSS
* aStatus
, ClientStreamData aData
) :
95 clientStreamNode::~clientStreamNode()
97 debugs(87, 3, "Freeing clientStreamNode " << this);
104 \ingroup ClientStreamInternal
105 * Initialise a client Stream.
107 * func is the read function for the head
108 * callback is the callback for the tail
109 * tailbuf and taillen are the initial buffer and length for the tail.
112 clientStreamInit(dlink_list
* list
, CSR
* func
, CSD
* rdetach
, CSS
* readstatus
,
113 ClientStreamData readdata
, CSCB
* callback
, CSD
* cdetach
, ClientStreamData callbackdata
,
114 StoreIOBuffer tailBuffer
)
116 clientStreamNode
*temp
= new clientStreamNode(func
, NULL
, rdetach
, readstatus
, readdata
);
117 dlinkAdd(cbdataReference(temp
), &temp
->node
, list
);
119 clientStreamInsertHead(list
, NULL
, callback
, cdetach
, NULL
, callbackdata
);
120 temp
= (clientStreamNode
*)list
->tail
->data
;
121 temp
->readBuffer
= tailBuffer
;
125 \ingroup ClientStreamInternal
126 * Doesn't actually insert at head. Instead it inserts one *after*
127 * head. This is because HEAD is a special node, as is tail
128 * This function is not suitable for inserting the real HEAD.
131 clientStreamInsertHead(dlink_list
* list
, CSR
* func
, CSCB
* callback
,
132 CSD
* detach
, CSS
* status
, ClientStreamData data
)
134 /* test preconditions */
135 assert(list
!= NULL
);
137 clientStreamNode
*temp
= new clientStreamNode(func
, callback
, detach
, status
, data
);
139 debugs(87, 3, "clientStreamInsertHead: Inserted node " << temp
<<
140 " with data " << data
.getRaw() << " after head");
142 if (list
->head
->next
)
143 temp
->readBuffer
= ((clientStreamNode
*)list
->head
->next
->data
)->readBuffer
;
145 dlinkAddAfter(cbdataReference(temp
), &temp
->node
, list
->head
, list
);
150 clientStreamCallback(clientStreamNode
* thisObject
, ClientHttpRequest
* http
,
151 HttpReply
* rep
, StoreIOBuffer replyBuffer
)
153 clientStreamNode
*next
;
154 assert(thisObject
&& http
&& thisObject
->node
.next
);
155 next
= thisObject
->next();
157 debugs(87, 3, "clientStreamCallback: Calling " << next
->callback
<< " with cbdata " <<
158 next
->data
.getRaw() << " from node " << thisObject
);
159 next
->callback(next
, http
, rep
, replyBuffer
);
163 \ingroup ClientStreamInternal
164 * Call the previous node in the chain to read some data
171 clientStreamRead(clientStreamNode
* thisObject
, ClientHttpRequest
* http
,
172 StoreIOBuffer readBuffer
)
174 /* place the parameters on the 'stack' */
175 clientStreamNode
*prev
;
176 assert(thisObject
&& http
&& thisObject
->prev());
177 prev
= thisObject
->prev();
179 debugs(87, 3, "clientStreamRead: Calling " << prev
->readfunc
<<
180 " with cbdata " << prev
->data
.getRaw() << " from node " << thisObject
);
181 thisObject
->readBuffer
= readBuffer
;
182 prev
->readfunc(prev
, http
);
186 \ingroup ClientStreamInternal
187 * Detach from the stream - only allowed for terminal members
193 clientStreamDetach(clientStreamNode
* thisObject
, ClientHttpRequest
* http
)
195 clientStreamNode
*temp
= thisObject
;
197 assert(thisObject
->node
.next
== NULL
);
198 debugs(87, 3, "clientStreamDetach: Detaching node " << thisObject
);
199 /* And clean up thisObject node */
200 /* ESI TODO: push refcount class through to head */
201 clientStreamNode
*prev
= NULL
;
203 if (thisObject
->prev())
204 prev
= cbdataReference(thisObject
->prev());
206 thisObject
->removeFromStream();
208 cbdataReferenceDone(temp
);
212 /* and tell the prev that the detach has occurred */
214 * We do it in thisObject order so that the detaching node is always
215 * at the end of the list
219 debugs(87, 3, "clientStreamDetach: Calling " << prev
->detach
<< " with cbdata " << prev
->data
.getRaw());
221 if (cbdataReferenceValid(prev
))
222 prev
->detach(prev
, http
);
224 cbdataReferenceDone(prev
);
229 \ingroup ClientStreamInternal
230 * Abort the stream - detach every node in the pipeline.
236 clientStreamAbort(clientStreamNode
* thisObject
, ClientHttpRequest
* http
)
240 assert(thisObject
!= NULL
);
241 assert(http
!= NULL
);
242 list
= thisObject
->head
;
243 debugs(87, 3, "clientStreamAbort: Aborting stream with tail " << list
->tail
);
246 clientStreamDetach((clientStreamNode
*)list
->tail
->data
, http
);
251 \ingroup ClientStreamInternal
252 * Call the upstream node to find it's status
257 clientStream_status_t
258 clientStreamStatus(clientStreamNode
* thisObject
, ClientHttpRequest
* http
)
260 clientStreamNode
*prev
;
261 assert(thisObject
&& http
&& thisObject
->node
.prev
);
262 prev
= (clientStreamNode
*)thisObject
->node
.prev
->data
;
263 return prev
->status(prev
, http
);
267 clientStreamNode::removeFromStream()
270 dlinkDelete(&node
, head
);
276 clientStreamNode::prev() const
279 return (clientStreamNode
*)node
.prev
->data
;
285 clientStreamNode::next() const
288 return (clientStreamNode
*)node
.next
->data
;