Skip to content

Commit 48d306a

Browse files
authored
add flow_collect, a version of collect that respects flow control Fixes twisted#238
1 parent 2ed0d46 commit 48d306a

File tree

1 file changed

+96
-1
lines changed

1 file changed

+96
-1
lines changed

src/treq/content.py

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import cgi
44
import json
55

6-
from twisted.internet.defer import Deferred, succeed
6+
from twisted.internet.defer import Deferred, succeed, inlineCallbacks
77

88
from twisted.internet.protocol import Protocol
99
from twisted.web.client import ResponseDone
@@ -65,6 +65,101 @@ def collect(response, collector):
6565
return d
6666

6767

68+
from twisted.internet import defer
69+
70+
from twisted.internet.protocol import Protocol
71+
from twisted.web.client import ResponseDone
72+
from twisted.web.http import PotentialDataLoss
73+
74+
75+
class _FlowBodyCollector(Protocol):
76+
def __init__(self, finished, collector):
77+
self.buffer = b''
78+
self.writing = False
79+
self.finished = finished
80+
self.collector = collector
81+
82+
@inlineCallbacks
83+
def dataReceived(self, data):
84+
try:
85+
self.buffer += data
86+
if self.writing:
87+
return
88+
89+
w = Deferred()
90+
self.writing = w
91+
self.transport.pauseProducing()
92+
while self.buffer:
93+
bufferred = self.buffer
94+
self.buffer = b''
95+
yield self.collector(bufferred)
96+
self.writing = False
97+
self.transport.resumeProducing()
98+
w.callback(None)
99+
except Exception as e:
100+
self.finished.errback(e)
101+
self.transport.stopProducing()
102+
103+
104+
@inlineCallbacks
105+
def connectionLost(self, reason):
106+
if self.finished.called:
107+
return
108+
yield self.writing
109+
110+
if reason.check(ResponseDone):
111+
self.finished.callback(None)
112+
elif reason.check(PotentialDataLoss):
113+
# http://twistedmatrix.com/trac/ticket/4840
114+
self.finished.callback(None)
115+
else:
116+
self.finished.errback(reason)
117+
118+
119+
def flow_collect(response, collector):
120+
"""
121+
Incrementally collect the body of the response. Respecting flow control.
122+
123+
This function may only be called **once** for a given response.
124+
125+
:param IResponse response: The HTTP response to collect the body from.
126+
:param collector: A callable that returns a deferred to be called each time
127+
data is available from the response body.
128+
:type collector: single argument callable
129+
130+
:rtype: Deferred that fires with None when the entire body has been read.
131+
"""
132+
if response.length == 0:
133+
return succeed(None)
134+
135+
d = Deferred()
136+
response.deliverBody(_FlowBodyCollector(d, collector))
137+
return d
138+
139+
140+
@inlineCallbacks
141+
def reduce(reducer, response, initializer):
142+
"""
143+
Incrementally collect the body of the response. Respecting flow control.
144+
145+
This function may only be called **once** for a given response.
146+
147+
:param IResponse response: The HTTP response to collect the body from.
148+
:param reducer: A reducer function called with accumulator and next data
149+
:type collector: two argument callable
150+
151+
:rtype: Deferred that fires with the accumulator when the entire body has been read.
152+
"""
153+
ref = [initializer, ] # py2 nonlocal
154+
155+
@inlineCallbacks
156+
def collector(data):
157+
ref[0] = yield reducer(ref[0], data)
158+
159+
yield collect(response, collector)
160+
defer.returnValue(ref[0])
161+
162+
68163
def content(response):
69164
"""
70165
Read the contents of an HTTP response.

0 commit comments

Comments
 (0)