##// END OF EJS Templates
http2: make read use pushchunk/popchunk, eschew itertools...
Brendan Cully -
r19037:1fde25ad default
parent child Browse files
Show More
@@ -1,207 +1,206 b''
1 1 # Copyright 2011, Google Inc.
2 2 # All rights reserved.
3 3 #
4 4 # Redistribution and use in source and binary forms, with or without
5 5 # modification, are permitted provided that the following conditions are
6 6 # met:
7 7 #
8 8 # * Redistributions of source code must retain the above copyright
9 9 # notice, this list of conditions and the following disclaimer.
10 10 # * Redistributions in binary form must reproduce the above
11 11 # copyright notice, this list of conditions and the following disclaimer
12 12 # in the documentation and/or other materials provided with the
13 13 # distribution.
14 14 # * Neither the name of Google Inc. nor the names of its
15 15 # contributors may be used to endorse or promote products derived from
16 16 # this software without specific prior written permission.
17 17
18 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 29 """Reader objects to abstract out different body response types.
30 30
31 31 This module is package-private. It is not expected that these will
32 32 have any clients outside of httpplus.
33 33 """
34 34
35 35 import httplib
36 36 import itertools
37 37 import logging
38 38
39 39 logger = logging.getLogger(__name__)
40 40
41 41
42 42 class ReadNotReady(Exception):
43 43 """Raised when read() is attempted but not enough data is loaded."""
44 44
45 45
46 46 class HTTPRemoteClosedError(httplib.HTTPException):
47 47 """The server closed the remote socket in the middle of a response."""
48 48
49 49
50 50 class AbstractReader(object):
51 51 """Abstract base class for response readers.
52 52
53 53 Subclasses must implement _load, and should implement _close if
54 54 it's not an error for the server to close their socket without
55 55 some termination condition being detected during _load.
56 56 """
57 57 def __init__(self):
58 58 self._finished = False
59 59 self._done_chunks = []
60 60 self.available_data = 0
61 61
62 62 def addchunk(self, data):
63 63 self._done_chunks.append(data)
64 64 self.available_data += len(data)
65 65
66 66 def pushchunk(self, data):
67 67 self._done_chunks.insert(0, data)
68 68 self.available_data += len(data)
69 69
70 70 def popchunk(self):
71 71 b = self._done_chunks.pop(0)
72 72 self.available_data -= len(b)
73 73
74 74 return b
75 75
76 76 def done(self):
77 77 return self._finished
78 78
79 79 def read(self, amt):
80 80 if self.available_data < amt and not self._finished:
81 81 raise ReadNotReady()
82 need = [amt]
83 def pred(s):
84 needed = need[0] > 0
85 need[0] -= len(s)
86 return needed
87 blocks = list(itertools.takewhile(pred, self._done_chunks))
88 self._done_chunks = self._done_chunks[len(blocks):]
89 over_read = sum(map(len, blocks)) - amt
90 if over_read > 0 and blocks:
91 logger.debug('need to reinsert %d data into done chunks', over_read)
92 last = blocks[-1]
93 blocks[-1], reinsert = last[:-over_read], last[-over_read:]
94 self._done_chunks.insert(0, reinsert)
82 blocks = []
83 need = amt
84 while self._done_chunks:
85 b = self.popchunk()
86 if len(b) > need:
87 nb = b[:need]
88 self.pushchunk(b[need:])
89 b = nb
90 blocks.append(b)
91 need -= len(b)
92 if need == 0:
93 break
95 94 result = ''.join(blocks)
96 95 assert len(result) == amt or (self._finished and len(result) < amt)
97 self.available_data -= amt
96
98 97 return result
99 98
100 99 def _load(self, data): # pragma: no cover
101 100 """Subclasses must implement this.
102 101
103 102 As data is available to be read out of this object, it should
104 103 be placed into the _done_chunks list. Subclasses should not
105 104 rely on data remaining in _done_chunks forever, as it may be
106 105 reaped if the client is parsing data as it comes in.
107 106 """
108 107 raise NotImplementedError
109 108
110 109 def _close(self):
111 110 """Default implementation of close.
112 111
113 112 The default implementation assumes that the reader will mark
114 113 the response as finished on the _finished attribute once the
115 114 entire response body has been read. In the event that this is
116 115 not true, the subclass should override the implementation of
117 116 close (for example, close-is-end responses have to set
118 117 self._finished in the close handler.)
119 118 """
120 119 if not self._finished:
121 120 raise HTTPRemoteClosedError(
122 121 'server appears to have closed the socket mid-response')
123 122
124 123
125 124 class AbstractSimpleReader(AbstractReader):
126 125 """Abstract base class for simple readers that require no response decoding.
127 126
128 127 Examples of such responses are Connection: Close (close-is-end)
129 128 and responses that specify a content length.
130 129 """
131 130 def _load(self, data):
132 131 if data:
133 132 assert not self._finished, (
134 133 'tried to add data (%r) to a closed reader!' % data)
135 134 logger.debug('%s read an additional %d data', self.name, len(data))
136 135 self.addchunk(data)
137 136
138 137
139 138 class CloseIsEndReader(AbstractSimpleReader):
140 139 """Reader for responses that specify Connection: Close for length."""
141 140 name = 'close-is-end'
142 141
143 142 def _close(self):
144 143 logger.info('Marking close-is-end reader as closed.')
145 144 self._finished = True
146 145
147 146
148 147 class ContentLengthReader(AbstractSimpleReader):
149 148 """Reader for responses that specify an exact content length."""
150 149 name = 'content-length'
151 150
152 151 def __init__(self, amount):
153 152 AbstractReader.__init__(self)
154 153 self._amount = amount
155 154 if amount == 0:
156 155 self._finished = True
157 156 self._amount_seen = 0
158 157
159 158 def _load(self, data):
160 159 AbstractSimpleReader._load(self, data)
161 160 self._amount_seen += len(data)
162 161 if self._amount_seen >= self._amount:
163 162 self._finished = True
164 163 logger.debug('content-length read complete')
165 164
166 165
167 166 class ChunkedReader(AbstractReader):
168 167 """Reader for chunked transfer encoding responses."""
169 168 def __init__(self, eol):
170 169 AbstractReader.__init__(self)
171 170 self._eol = eol
172 171 self._leftover_skip_amt = 0
173 172 self._leftover_data = ''
174 173
175 174 def _load(self, data):
176 175 assert not self._finished, 'tried to add data to a closed reader!'
177 176 logger.debug('chunked read an additional %d data', len(data))
178 177 position = 0
179 178 if self._leftover_data:
180 179 logger.debug('chunked reader trying to finish block from leftover data')
181 180 # TODO: avoid this string concatenation if possible
182 181 data = self._leftover_data + data
183 182 position = self._leftover_skip_amt
184 183 self._leftover_data = ''
185 184 self._leftover_skip_amt = 0
186 185 datalen = len(data)
187 186 while position < datalen:
188 187 split = data.find(self._eol, position)
189 188 if split == -1:
190 189 self._leftover_data = data
191 190 self._leftover_skip_amt = position
192 191 return
193 192 amt = int(data[position:split], base=16)
194 193 block_start = split + len(self._eol)
195 194 # If the whole data chunk plus the eol trailer hasn't
196 195 # loaded, we'll wait for the next load.
197 196 if block_start + amt + len(self._eol) > len(data):
198 197 self._leftover_data = data
199 198 self._leftover_skip_amt = position
200 199 return
201 200 if amt == 0:
202 201 self._finished = True
203 202 logger.debug('closing chunked reader due to chunk of length 0')
204 203 return
205 204 self.addchunk(data[block_start:block_start + amt])
206 205 position = block_start + amt + len(self._eol)
207 206 # no-check-code
General Comments 0
You need to be logged in to leave comments. Login now