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