##// END OF EJS Templates
keepalive: add `__repr__()` to the HTTPConnection class to ease debugging...
Matt Harbison -
r50437:76fbb1b6 stable
parent child Browse files
Show More
@@ -1,847 +1,858 b''
1 # This library is free software; you can redistribute it and/or
1 # This library is free software; you can redistribute it and/or
2 # modify it under the terms of the GNU Lesser General Public
2 # modify it under the terms of the GNU Lesser General Public
3 # License as published by the Free Software Foundation; either
3 # License as published by the Free Software Foundation; either
4 # version 2.1 of the License, or (at your option) any later version.
4 # version 2.1 of the License, or (at your option) any later version.
5 #
5 #
6 # This library is distributed in the hope that it will be useful,
6 # This library is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 # Lesser General Public License for more details.
9 # Lesser General Public License for more details.
10 #
10 #
11 # You should have received a copy of the GNU Lesser General Public
11 # You should have received a copy of the GNU Lesser General Public
12 # License along with this library; if not, see
12 # License along with this library; if not, see
13 # <http://www.gnu.org/licenses/>.
13 # <http://www.gnu.org/licenses/>.
14
14
15 # This file is part of urlgrabber, a high-level cross-protocol url-grabber
15 # This file is part of urlgrabber, a high-level cross-protocol url-grabber
16 # Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko
16 # Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko
17
17
18 # Modified by Benoit Boissinot:
18 # Modified by Benoit Boissinot:
19 # - fix for digest auth (inspired from urllib2.py @ Python v2.4)
19 # - fix for digest auth (inspired from urllib2.py @ Python v2.4)
20 # Modified by Dirkjan Ochtman:
20 # Modified by Dirkjan Ochtman:
21 # - import md5 function from a local util module
21 # - import md5 function from a local util module
22 # Modified by Augie Fackler:
22 # Modified by Augie Fackler:
23 # - add safesend method and use it to prevent broken pipe errors
23 # - add safesend method and use it to prevent broken pipe errors
24 # on large POST requests
24 # on large POST requests
25
25
26 """An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive.
26 """An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive.
27
27
28 >>> import urllib2
28 >>> import urllib2
29 >>> from keepalive import HTTPHandler
29 >>> from keepalive import HTTPHandler
30 >>> keepalive_handler = HTTPHandler()
30 >>> keepalive_handler = HTTPHandler()
31 >>> opener = urlreq.buildopener(keepalive_handler)
31 >>> opener = urlreq.buildopener(keepalive_handler)
32 >>> urlreq.installopener(opener)
32 >>> urlreq.installopener(opener)
33 >>>
33 >>>
34 >>> fo = urlreq.urlopen('http://www.python.org')
34 >>> fo = urlreq.urlopen('http://www.python.org')
35
35
36 If a connection to a given host is requested, and all of the existing
36 If a connection to a given host is requested, and all of the existing
37 connections are still in use, another connection will be opened. If
37 connections are still in use, another connection will be opened. If
38 the handler tries to use an existing connection but it fails in some
38 the handler tries to use an existing connection but it fails in some
39 way, it will be closed and removed from the pool.
39 way, it will be closed and removed from the pool.
40
40
41 To remove the handler, simply re-run build_opener with no arguments, and
41 To remove the handler, simply re-run build_opener with no arguments, and
42 install that opener.
42 install that opener.
43
43
44 You can explicitly close connections by using the close_connection()
44 You can explicitly close connections by using the close_connection()
45 method of the returned file-like object (described below) or you can
45 method of the returned file-like object (described below) or you can
46 use the handler methods:
46 use the handler methods:
47
47
48 close_connection(host)
48 close_connection(host)
49 close_all()
49 close_all()
50 open_connections()
50 open_connections()
51
51
52 NOTE: using the close_connection and close_all methods of the handler
52 NOTE: using the close_connection and close_all methods of the handler
53 should be done with care when using multiple threads.
53 should be done with care when using multiple threads.
54 * there is nothing that prevents another thread from creating new
54 * there is nothing that prevents another thread from creating new
55 connections immediately after connections are closed
55 connections immediately after connections are closed
56 * no checks are done to prevent in-use connections from being closed
56 * no checks are done to prevent in-use connections from being closed
57
57
58 >>> keepalive_handler.close_all()
58 >>> keepalive_handler.close_all()
59
59
60 EXTRA ATTRIBUTES AND METHODS
60 EXTRA ATTRIBUTES AND METHODS
61
61
62 Upon a status of 200, the object returned has a few additional
62 Upon a status of 200, the object returned has a few additional
63 attributes and methods, which should not be used if you want to
63 attributes and methods, which should not be used if you want to
64 remain consistent with the normal urllib2-returned objects:
64 remain consistent with the normal urllib2-returned objects:
65
65
66 close_connection() - close the connection to the host
66 close_connection() - close the connection to the host
67 readlines() - you know, readlines()
67 readlines() - you know, readlines()
68 status - the return status (i.e. 404)
68 status - the return status (i.e. 404)
69 reason - english translation of status (i.e. 'File not found')
69 reason - english translation of status (i.e. 'File not found')
70
70
71 If you want the best of both worlds, use this inside an
71 If you want the best of both worlds, use this inside an
72 AttributeError-catching try:
72 AttributeError-catching try:
73
73
74 >>> try: status = fo.status
74 >>> try: status = fo.status
75 >>> except AttributeError: status = None
75 >>> except AttributeError: status = None
76
76
77 Unfortunately, these are ONLY there if status == 200, so it's not
77 Unfortunately, these are ONLY there if status == 200, so it's not
78 easy to distinguish between non-200 responses. The reason is that
78 easy to distinguish between non-200 responses. The reason is that
79 urllib2 tries to do clever things with error codes 301, 302, 401,
79 urllib2 tries to do clever things with error codes 301, 302, 401,
80 and 407, and it wraps the object upon return.
80 and 407, and it wraps the object upon return.
81 """
81 """
82
82
83 # $Id: keepalive.py,v 1.14 2006/04/04 21:00:32 mstenner Exp $
83 # $Id: keepalive.py,v 1.14 2006/04/04 21:00:32 mstenner Exp $
84
84
85
85
86 import collections
86 import collections
87 import hashlib
87 import hashlib
88 import socket
88 import socket
89 import sys
89 import sys
90 import threading
90 import threading
91
91
92 from .i18n import _
92 from .i18n import _
93 from .pycompat import getattr
93 from .pycompat import getattr
94 from .node import hex
94 from .node import hex
95 from . import (
95 from . import (
96 pycompat,
96 pycompat,
97 urllibcompat,
97 urllibcompat,
98 util,
98 util,
99 )
99 )
100 from .utils import procutil
100 from .utils import procutil
101
101
102 httplib = util.httplib
102 httplib = util.httplib
103 urlerr = util.urlerr
103 urlerr = util.urlerr
104 urlreq = util.urlreq
104 urlreq = util.urlreq
105
105
106 DEBUG = None
106 DEBUG = None
107
107
108
108
109 class ConnectionManager:
109 class ConnectionManager:
110 """
110 """
111 The connection manager must be able to:
111 The connection manager must be able to:
112 * keep track of all existing
112 * keep track of all existing
113 """
113 """
114
114
115 def __init__(self):
115 def __init__(self):
116 self._lock = threading.Lock()
116 self._lock = threading.Lock()
117 self._hostmap = collections.defaultdict(list) # host -> [connection]
117 self._hostmap = collections.defaultdict(list) # host -> [connection]
118 self._connmap = {} # map connections to host
118 self._connmap = {} # map connections to host
119 self._readymap = {} # map connection to ready state
119 self._readymap = {} # map connection to ready state
120
120
121 def add(self, host, connection, ready):
121 def add(self, host, connection, ready):
122 self._lock.acquire()
122 self._lock.acquire()
123 try:
123 try:
124 self._hostmap[host].append(connection)
124 self._hostmap[host].append(connection)
125 self._connmap[connection] = host
125 self._connmap[connection] = host
126 self._readymap[connection] = ready
126 self._readymap[connection] = ready
127 finally:
127 finally:
128 self._lock.release()
128 self._lock.release()
129
129
130 def remove(self, connection):
130 def remove(self, connection):
131 self._lock.acquire()
131 self._lock.acquire()
132 try:
132 try:
133 try:
133 try:
134 host = self._connmap[connection]
134 host = self._connmap[connection]
135 except KeyError:
135 except KeyError:
136 pass
136 pass
137 else:
137 else:
138 del self._connmap[connection]
138 del self._connmap[connection]
139 del self._readymap[connection]
139 del self._readymap[connection]
140 self._hostmap[host].remove(connection)
140 self._hostmap[host].remove(connection)
141 if not self._hostmap[host]:
141 if not self._hostmap[host]:
142 del self._hostmap[host]
142 del self._hostmap[host]
143 finally:
143 finally:
144 self._lock.release()
144 self._lock.release()
145
145
146 def set_ready(self, connection, ready):
146 def set_ready(self, connection, ready):
147 try:
147 try:
148 self._readymap[connection] = ready
148 self._readymap[connection] = ready
149 except KeyError:
149 except KeyError:
150 pass
150 pass
151
151
152 def get_ready_conn(self, host):
152 def get_ready_conn(self, host):
153 conn = None
153 conn = None
154 self._lock.acquire()
154 self._lock.acquire()
155 try:
155 try:
156 for c in self._hostmap[host]:
156 for c in self._hostmap[host]:
157 if self._readymap[c]:
157 if self._readymap[c]:
158 self._readymap[c] = False
158 self._readymap[c] = False
159 conn = c
159 conn = c
160 break
160 break
161 finally:
161 finally:
162 self._lock.release()
162 self._lock.release()
163 return conn
163 return conn
164
164
165 def get_all(self, host=None):
165 def get_all(self, host=None):
166 if host:
166 if host:
167 return list(self._hostmap[host])
167 return list(self._hostmap[host])
168 else:
168 else:
169 return dict(
169 return dict(
170 {h: list(conns) for (h, conns) in self._hostmap.items()}
170 {h: list(conns) for (h, conns) in self._hostmap.items()}
171 )
171 )
172
172
173
173
174 class KeepAliveHandler:
174 class KeepAliveHandler:
175 def __init__(self, timeout=None):
175 def __init__(self, timeout=None):
176 self._cm = ConnectionManager()
176 self._cm = ConnectionManager()
177 self._timeout = timeout
177 self._timeout = timeout
178 self.requestscount = 0
178 self.requestscount = 0
179 self.sentbytescount = 0
179 self.sentbytescount = 0
180
180
181 #### Connection Management
181 #### Connection Management
182 def open_connections(self):
182 def open_connections(self):
183 """return a list of connected hosts and the number of connections
183 """return a list of connected hosts and the number of connections
184 to each. [('foo.com:80', 2), ('bar.org', 1)]"""
184 to each. [('foo.com:80', 2), ('bar.org', 1)]"""
185 return [(host, len(li)) for (host, li) in self._cm.get_all().items()]
185 return [(host, len(li)) for (host, li) in self._cm.get_all().items()]
186
186
187 def close_connection(self, host):
187 def close_connection(self, host):
188 """close connection(s) to <host>
188 """close connection(s) to <host>
189 host is the host:port spec, as in 'www.cnn.com:8080' as passed in.
189 host is the host:port spec, as in 'www.cnn.com:8080' as passed in.
190 no error occurs if there is no connection to that host."""
190 no error occurs if there is no connection to that host."""
191 for h in self._cm.get_all(host):
191 for h in self._cm.get_all(host):
192 self._cm.remove(h)
192 self._cm.remove(h)
193 h.close()
193 h.close()
194
194
195 def close_all(self):
195 def close_all(self):
196 """close all open connections"""
196 """close all open connections"""
197 for host, conns in self._cm.get_all().items():
197 for host, conns in self._cm.get_all().items():
198 for h in conns:
198 for h in conns:
199 self._cm.remove(h)
199 self._cm.remove(h)
200 h.close()
200 h.close()
201
201
202 def _request_closed(self, request, host, connection):
202 def _request_closed(self, request, host, connection):
203 """tells us that this request is now closed and that the
203 """tells us that this request is now closed and that the
204 connection is ready for another request"""
204 connection is ready for another request"""
205 self._cm.set_ready(connection, True)
205 self._cm.set_ready(connection, True)
206
206
207 def _remove_connection(self, host, connection, close=0):
207 def _remove_connection(self, host, connection, close=0):
208 if close:
208 if close:
209 connection.close()
209 connection.close()
210 self._cm.remove(connection)
210 self._cm.remove(connection)
211
211
212 #### Transaction Execution
212 #### Transaction Execution
213 def http_open(self, req):
213 def http_open(self, req):
214 return self.do_open(HTTPConnection, req)
214 return self.do_open(HTTPConnection, req)
215
215
216 def do_open(self, http_class, req):
216 def do_open(self, http_class, req):
217 host = urllibcompat.gethost(req)
217 host = urllibcompat.gethost(req)
218 if not host:
218 if not host:
219 raise urlerr.urlerror(b'no host given')
219 raise urlerr.urlerror(b'no host given')
220
220
221 try:
221 try:
222 h = self._cm.get_ready_conn(host)
222 h = self._cm.get_ready_conn(host)
223 while h:
223 while h:
224 r = self._reuse_connection(h, req, host)
224 r = self._reuse_connection(h, req, host)
225
225
226 # if this response is non-None, then it worked and we're
226 # if this response is non-None, then it worked and we're
227 # done. Break out, skipping the else block.
227 # done. Break out, skipping the else block.
228 if r:
228 if r:
229 break
229 break
230
230
231 # connection is bad - possibly closed by server
231 # connection is bad - possibly closed by server
232 # discard it and ask for the next free connection
232 # discard it and ask for the next free connection
233 h.close()
233 h.close()
234 self._cm.remove(h)
234 self._cm.remove(h)
235 h = self._cm.get_ready_conn(host)
235 h = self._cm.get_ready_conn(host)
236 else:
236 else:
237 # no (working) free connections were found. Create a new one.
237 # no (working) free connections were found. Create a new one.
238 h = http_class(host, timeout=self._timeout)
238 h = http_class(host, timeout=self._timeout)
239 if DEBUG:
239 if DEBUG:
240 DEBUG.info(
240 DEBUG.info(
241 b"creating new connection to %s (%d)", host, id(h)
241 b"creating new connection to %s (%d)", host, id(h)
242 )
242 )
243 self._cm.add(host, h, False)
243 self._cm.add(host, h, False)
244 self._start_transaction(h, req)
244 self._start_transaction(h, req)
245 r = h.getresponse()
245 r = h.getresponse()
246 # The string form of BadStatusLine is the status line. Add some context
246 # The string form of BadStatusLine is the status line. Add some context
247 # to make the error message slightly more useful.
247 # to make the error message slightly more useful.
248 except httplib.BadStatusLine as err:
248 except httplib.BadStatusLine as err:
249 raise urlerr.urlerror(
249 raise urlerr.urlerror(
250 _(b'bad HTTP status line: %s') % pycompat.sysbytes(err.line)
250 _(b'bad HTTP status line: %s') % pycompat.sysbytes(err.line)
251 )
251 )
252 except (socket.error, httplib.HTTPException) as err:
252 except (socket.error, httplib.HTTPException) as err:
253 raise urlerr.urlerror(err)
253 raise urlerr.urlerror(err)
254
254
255 # If not a persistent connection, don't try to reuse it. Look
255 # If not a persistent connection, don't try to reuse it. Look
256 # for this using getattr() since vcr doesn't define this
256 # for this using getattr() since vcr doesn't define this
257 # attribute, and in that case always close the connection.
257 # attribute, and in that case always close the connection.
258 if getattr(r, 'will_close', True):
258 if getattr(r, 'will_close', True):
259 self._cm.remove(h)
259 self._cm.remove(h)
260
260
261 if DEBUG:
261 if DEBUG:
262 DEBUG.info(b"STATUS: %s, %s", r.status, r.reason)
262 DEBUG.info(b"STATUS: %s, %s", r.status, r.reason)
263 r._handler = self
263 r._handler = self
264 r._host = host
264 r._host = host
265 r._url = req.get_full_url()
265 r._url = req.get_full_url()
266 r._connection = h
266 r._connection = h
267 r.code = r.status
267 r.code = r.status
268 r.headers = r.msg
268 r.headers = r.msg
269 r.msg = r.reason
269 r.msg = r.reason
270
270
271 return r
271 return r
272
272
273 def _reuse_connection(self, h, req, host):
273 def _reuse_connection(self, h, req, host):
274 """start the transaction with a re-used connection
274 """start the transaction with a re-used connection
275 return a response object (r) upon success or None on failure.
275 return a response object (r) upon success or None on failure.
276 This DOES not close or remove bad connections in cases where
276 This DOES not close or remove bad connections in cases where
277 it returns. However, if an unexpected exception occurs, it
277 it returns. However, if an unexpected exception occurs, it
278 will close and remove the connection before re-raising.
278 will close and remove the connection before re-raising.
279 """
279 """
280 try:
280 try:
281 self._start_transaction(h, req)
281 self._start_transaction(h, req)
282 r = h.getresponse()
282 r = h.getresponse()
283 # note: just because we got something back doesn't mean it
283 # note: just because we got something back doesn't mean it
284 # worked. We'll check the version below, too.
284 # worked. We'll check the version below, too.
285 except (socket.error, httplib.HTTPException):
285 except (socket.error, httplib.HTTPException):
286 r = None
286 r = None
287 except: # re-raises
287 except: # re-raises
288 # adding this block just in case we've missed
288 # adding this block just in case we've missed
289 # something we will still raise the exception, but
289 # something we will still raise the exception, but
290 # lets try and close the connection and remove it
290 # lets try and close the connection and remove it
291 # first. We previously got into a nasty loop
291 # first. We previously got into a nasty loop
292 # where an exception was uncaught, and so the
292 # where an exception was uncaught, and so the
293 # connection stayed open. On the next try, the
293 # connection stayed open. On the next try, the
294 # same exception was raised, etc. The trade-off is
294 # same exception was raised, etc. The trade-off is
295 # that it's now possible this call will raise
295 # that it's now possible this call will raise
296 # a DIFFERENT exception
296 # a DIFFERENT exception
297 if DEBUG:
297 if DEBUG:
298 DEBUG.error(
298 DEBUG.error(
299 b"unexpected exception - closing connection to %s (%d)",
299 b"unexpected exception - closing connection to %s (%d)",
300 host,
300 host,
301 id(h),
301 id(h),
302 )
302 )
303 self._cm.remove(h)
303 self._cm.remove(h)
304 h.close()
304 h.close()
305 raise
305 raise
306
306
307 if r is None or r.version == 9:
307 if r is None or r.version == 9:
308 # httplib falls back to assuming HTTP 0.9 if it gets a
308 # httplib falls back to assuming HTTP 0.9 if it gets a
309 # bad header back. This is most likely to happen if
309 # bad header back. This is most likely to happen if
310 # the socket has been closed by the server since we
310 # the socket has been closed by the server since we
311 # last used the connection.
311 # last used the connection.
312 if DEBUG:
312 if DEBUG:
313 DEBUG.info(
313 DEBUG.info(
314 b"failed to re-use connection to %s (%d)", host, id(h)
314 b"failed to re-use connection to %s (%d)", host, id(h)
315 )
315 )
316 r = None
316 r = None
317 else:
317 else:
318 if DEBUG:
318 if DEBUG:
319 DEBUG.info(b"re-using connection to %s (%d)", host, id(h))
319 DEBUG.info(b"re-using connection to %s (%d)", host, id(h))
320
320
321 return r
321 return r
322
322
323 def _start_transaction(self, h, req):
323 def _start_transaction(self, h, req):
324 oldbytescount = getattr(h, 'sentbytescount', 0)
324 oldbytescount = getattr(h, 'sentbytescount', 0)
325
325
326 # What follows mostly reimplements HTTPConnection.request()
326 # What follows mostly reimplements HTTPConnection.request()
327 # except it adds self.parent.addheaders in the mix and sends headers
327 # except it adds self.parent.addheaders in the mix and sends headers
328 # in a deterministic order (to make testing easier).
328 # in a deterministic order (to make testing easier).
329 headers = util.sortdict(self.parent.addheaders)
329 headers = util.sortdict(self.parent.addheaders)
330 headers.update(sorted(req.headers.items()))
330 headers.update(sorted(req.headers.items()))
331 headers.update(sorted(req.unredirected_hdrs.items()))
331 headers.update(sorted(req.unredirected_hdrs.items()))
332 headers = util.sortdict((n.lower(), v) for n, v in headers.items())
332 headers = util.sortdict((n.lower(), v) for n, v in headers.items())
333 skipheaders = {}
333 skipheaders = {}
334 for n in ('host', 'accept-encoding'):
334 for n in ('host', 'accept-encoding'):
335 if n in headers:
335 if n in headers:
336 skipheaders['skip_' + n.replace('-', '_')] = 1
336 skipheaders['skip_' + n.replace('-', '_')] = 1
337 try:
337 try:
338 if urllibcompat.hasdata(req):
338 if urllibcompat.hasdata(req):
339 data = urllibcompat.getdata(req)
339 data = urllibcompat.getdata(req)
340 h.putrequest(
340 h.putrequest(
341 req.get_method(),
341 req.get_method(),
342 urllibcompat.getselector(req),
342 urllibcompat.getselector(req),
343 **skipheaders
343 **skipheaders
344 )
344 )
345 if 'content-type' not in headers:
345 if 'content-type' not in headers:
346 h.putheader(
346 h.putheader(
347 'Content-type', 'application/x-www-form-urlencoded'
347 'Content-type', 'application/x-www-form-urlencoded'
348 )
348 )
349 if 'content-length' not in headers:
349 if 'content-length' not in headers:
350 h.putheader('Content-length', '%d' % len(data))
350 h.putheader('Content-length', '%d' % len(data))
351 else:
351 else:
352 h.putrequest(
352 h.putrequest(
353 req.get_method(),
353 req.get_method(),
354 urllibcompat.getselector(req),
354 urllibcompat.getselector(req),
355 **skipheaders
355 **skipheaders
356 )
356 )
357 except socket.error as err:
357 except socket.error as err:
358 raise urlerr.urlerror(err)
358 raise urlerr.urlerror(err)
359 for k, v in headers.items():
359 for k, v in headers.items():
360 h.putheader(k, v)
360 h.putheader(k, v)
361 h.endheaders()
361 h.endheaders()
362 if urllibcompat.hasdata(req):
362 if urllibcompat.hasdata(req):
363 h.send(data)
363 h.send(data)
364
364
365 # This will fail to record events in case of I/O failure. That's OK.
365 # This will fail to record events in case of I/O failure. That's OK.
366 self.requestscount += 1
366 self.requestscount += 1
367 self.sentbytescount += getattr(h, 'sentbytescount', 0) - oldbytescount
367 self.sentbytescount += getattr(h, 'sentbytescount', 0) - oldbytescount
368
368
369 try:
369 try:
370 self.parent.requestscount += 1
370 self.parent.requestscount += 1
371 self.parent.sentbytescount += (
371 self.parent.sentbytescount += (
372 getattr(h, 'sentbytescount', 0) - oldbytescount
372 getattr(h, 'sentbytescount', 0) - oldbytescount
373 )
373 )
374 except AttributeError:
374 except AttributeError:
375 pass
375 pass
376
376
377
377
378 class HTTPHandler(KeepAliveHandler, urlreq.httphandler):
378 class HTTPHandler(KeepAliveHandler, urlreq.httphandler):
379 pass
379 pass
380
380
381
381
382 class HTTPResponse(httplib.HTTPResponse):
382 class HTTPResponse(httplib.HTTPResponse):
383 # we need to subclass HTTPResponse in order to
383 # we need to subclass HTTPResponse in order to
384 # 1) add readline(), readlines(), and readinto() methods
384 # 1) add readline(), readlines(), and readinto() methods
385 # 2) add close_connection() methods
385 # 2) add close_connection() methods
386 # 3) add info() and geturl() methods
386 # 3) add info() and geturl() methods
387
387
388 # in order to add readline(), read must be modified to deal with a
388 # in order to add readline(), read must be modified to deal with a
389 # buffer. example: readline must read a buffer and then spit back
389 # buffer. example: readline must read a buffer and then spit back
390 # one line at a time. The only real alternative is to read one
390 # one line at a time. The only real alternative is to read one
391 # BYTE at a time (ick). Once something has been read, it can't be
391 # BYTE at a time (ick). Once something has been read, it can't be
392 # put back (ok, maybe it can, but that's even uglier than this),
392 # put back (ok, maybe it can, but that's even uglier than this),
393 # so if you THEN do a normal read, you must first take stuff from
393 # so if you THEN do a normal read, you must first take stuff from
394 # the buffer.
394 # the buffer.
395
395
396 # the read method wraps the original to accommodate buffering,
396 # the read method wraps the original to accommodate buffering,
397 # although read() never adds to the buffer.
397 # although read() never adds to the buffer.
398 # Both readline and readlines have been stolen with almost no
398 # Both readline and readlines have been stolen with almost no
399 # modification from socket.py
399 # modification from socket.py
400
400
401 def __init__(self, sock, debuglevel=0, strict=0, method=None):
401 def __init__(self, sock, debuglevel=0, strict=0, method=None):
402 httplib.HTTPResponse.__init__(
402 httplib.HTTPResponse.__init__(
403 self, sock, debuglevel=debuglevel, method=method
403 self, sock, debuglevel=debuglevel, method=method
404 )
404 )
405 self.fileno = sock.fileno
405 self.fileno = sock.fileno
406 self.code = None
406 self.code = None
407 self.receivedbytescount = 0
407 self.receivedbytescount = 0
408 self._rbuf = b''
408 self._rbuf = b''
409 self._rbufsize = 8096
409 self._rbufsize = 8096
410 self._handler = None # inserted by the handler later
410 self._handler = None # inserted by the handler later
411 self._host = None # (same)
411 self._host = None # (same)
412 self._url = None # (same)
412 self._url = None # (same)
413 self._connection = None # (same)
413 self._connection = None # (same)
414
414
415 _raw_read = httplib.HTTPResponse.read
415 _raw_read = httplib.HTTPResponse.read
416 _raw_readinto = getattr(httplib.HTTPResponse, 'readinto', None)
416 _raw_readinto = getattr(httplib.HTTPResponse, 'readinto', None)
417
417
418 # Python 2.7 has a single close() which closes the socket handle.
418 # Python 2.7 has a single close() which closes the socket handle.
419 # This method was effectively renamed to _close_conn() in Python 3. But
419 # This method was effectively renamed to _close_conn() in Python 3. But
420 # there is also a close(). _close_conn() is called by methods like
420 # there is also a close(). _close_conn() is called by methods like
421 # read().
421 # read().
422
422
423 def close(self):
423 def close(self):
424 if self.fp:
424 if self.fp:
425 self.fp.close()
425 self.fp.close()
426 self.fp = None
426 self.fp = None
427 if self._handler:
427 if self._handler:
428 self._handler._request_closed(
428 self._handler._request_closed(
429 self, self._host, self._connection
429 self, self._host, self._connection
430 )
430 )
431
431
432 def _close_conn(self):
432 def _close_conn(self):
433 self.close()
433 self.close()
434
434
435 def close_connection(self):
435 def close_connection(self):
436 self._handler._remove_connection(self._host, self._connection, close=1)
436 self._handler._remove_connection(self._host, self._connection, close=1)
437 self.close()
437 self.close()
438
438
439 def info(self):
439 def info(self):
440 return self.headers
440 return self.headers
441
441
442 def geturl(self):
442 def geturl(self):
443 return self._url
443 return self._url
444
444
445 def read(self, amt=None):
445 def read(self, amt=None):
446 # the _rbuf test is only in this first if for speed. It's not
446 # the _rbuf test is only in this first if for speed. It's not
447 # logically necessary
447 # logically necessary
448 if self._rbuf and amt is not None:
448 if self._rbuf and amt is not None:
449 L = len(self._rbuf)
449 L = len(self._rbuf)
450 if amt > L:
450 if amt > L:
451 amt -= L
451 amt -= L
452 else:
452 else:
453 s = self._rbuf[:amt]
453 s = self._rbuf[:amt]
454 self._rbuf = self._rbuf[amt:]
454 self._rbuf = self._rbuf[amt:]
455 return s
455 return s
456 # Careful! http.client.HTTPResponse.read() on Python 3 is
456 # Careful! http.client.HTTPResponse.read() on Python 3 is
457 # implemented using readinto(), which can duplicate self._rbuf
457 # implemented using readinto(), which can duplicate self._rbuf
458 # if it's not empty.
458 # if it's not empty.
459 s = self._rbuf
459 s = self._rbuf
460 self._rbuf = b''
460 self._rbuf = b''
461 data = self._raw_read(amt)
461 data = self._raw_read(amt)
462
462
463 self.receivedbytescount += len(data)
463 self.receivedbytescount += len(data)
464 try:
464 try:
465 self._connection.receivedbytescount += len(data)
465 self._connection.receivedbytescount += len(data)
466 except AttributeError:
466 except AttributeError:
467 pass
467 pass
468 try:
468 try:
469 self._handler.parent.receivedbytescount += len(data)
469 self._handler.parent.receivedbytescount += len(data)
470 except AttributeError:
470 except AttributeError:
471 pass
471 pass
472
472
473 s += data
473 s += data
474 return s
474 return s
475
475
476 # stolen from Python SVN #68532 to fix issue1088
476 # stolen from Python SVN #68532 to fix issue1088
477 def _read_chunked(self, amt):
477 def _read_chunked(self, amt):
478 chunk_left = self.chunk_left
478 chunk_left = self.chunk_left
479 parts = []
479 parts = []
480
480
481 while True:
481 while True:
482 if chunk_left is None:
482 if chunk_left is None:
483 line = self.fp.readline()
483 line = self.fp.readline()
484 i = line.find(b';')
484 i = line.find(b';')
485 if i >= 0:
485 if i >= 0:
486 line = line[:i] # strip chunk-extensions
486 line = line[:i] # strip chunk-extensions
487 try:
487 try:
488 chunk_left = int(line, 16)
488 chunk_left = int(line, 16)
489 except ValueError:
489 except ValueError:
490 # close the connection as protocol synchronization is
490 # close the connection as protocol synchronization is
491 # probably lost
491 # probably lost
492 self.close()
492 self.close()
493 raise httplib.IncompleteRead(b''.join(parts))
493 raise httplib.IncompleteRead(b''.join(parts))
494 if chunk_left == 0:
494 if chunk_left == 0:
495 break
495 break
496 if amt is None:
496 if amt is None:
497 parts.append(self._safe_read(chunk_left))
497 parts.append(self._safe_read(chunk_left))
498 elif amt < chunk_left:
498 elif amt < chunk_left:
499 parts.append(self._safe_read(amt))
499 parts.append(self._safe_read(amt))
500 self.chunk_left = chunk_left - amt
500 self.chunk_left = chunk_left - amt
501 return b''.join(parts)
501 return b''.join(parts)
502 elif amt == chunk_left:
502 elif amt == chunk_left:
503 parts.append(self._safe_read(amt))
503 parts.append(self._safe_read(amt))
504 self._safe_read(2) # toss the CRLF at the end of the chunk
504 self._safe_read(2) # toss the CRLF at the end of the chunk
505 self.chunk_left = None
505 self.chunk_left = None
506 return b''.join(parts)
506 return b''.join(parts)
507 else:
507 else:
508 parts.append(self._safe_read(chunk_left))
508 parts.append(self._safe_read(chunk_left))
509 amt -= chunk_left
509 amt -= chunk_left
510
510
511 # we read the whole chunk, get another
511 # we read the whole chunk, get another
512 self._safe_read(2) # toss the CRLF at the end of the chunk
512 self._safe_read(2) # toss the CRLF at the end of the chunk
513 chunk_left = None
513 chunk_left = None
514
514
515 # read and discard trailer up to the CRLF terminator
515 # read and discard trailer up to the CRLF terminator
516 ### note: we shouldn't have any trailers!
516 ### note: we shouldn't have any trailers!
517 while True:
517 while True:
518 line = self.fp.readline()
518 line = self.fp.readline()
519 if not line:
519 if not line:
520 # a vanishingly small number of sites EOF without
520 # a vanishingly small number of sites EOF without
521 # sending the trailer
521 # sending the trailer
522 break
522 break
523 if line == b'\r\n':
523 if line == b'\r\n':
524 break
524 break
525
525
526 # we read everything; close the "file"
526 # we read everything; close the "file"
527 self.close()
527 self.close()
528
528
529 return b''.join(parts)
529 return b''.join(parts)
530
530
531 def readline(self):
531 def readline(self):
532 # Fast path for a line is already available in read buffer.
532 # Fast path for a line is already available in read buffer.
533 i = self._rbuf.find(b'\n')
533 i = self._rbuf.find(b'\n')
534 if i >= 0:
534 if i >= 0:
535 i += 1
535 i += 1
536 line = self._rbuf[:i]
536 line = self._rbuf[:i]
537 self._rbuf = self._rbuf[i:]
537 self._rbuf = self._rbuf[i:]
538 return line
538 return line
539
539
540 # No newline in local buffer. Read until we find one.
540 # No newline in local buffer. Read until we find one.
541 # readinto read via readinto will already return _rbuf
541 # readinto read via readinto will already return _rbuf
542 if self._raw_readinto is None:
542 if self._raw_readinto is None:
543 chunks = [self._rbuf]
543 chunks = [self._rbuf]
544 else:
544 else:
545 chunks = []
545 chunks = []
546 i = -1
546 i = -1
547 readsize = self._rbufsize
547 readsize = self._rbufsize
548 while True:
548 while True:
549 new = self._raw_read(readsize)
549 new = self._raw_read(readsize)
550 if not new:
550 if not new:
551 break
551 break
552
552
553 self.receivedbytescount += len(new)
553 self.receivedbytescount += len(new)
554 self._connection.receivedbytescount += len(new)
554 self._connection.receivedbytescount += len(new)
555 try:
555 try:
556 self._handler.parent.receivedbytescount += len(new)
556 self._handler.parent.receivedbytescount += len(new)
557 except AttributeError:
557 except AttributeError:
558 pass
558 pass
559
559
560 chunks.append(new)
560 chunks.append(new)
561 i = new.find(b'\n')
561 i = new.find(b'\n')
562 if i >= 0:
562 if i >= 0:
563 break
563 break
564
564
565 # We either have exhausted the stream or have a newline in chunks[-1].
565 # We either have exhausted the stream or have a newline in chunks[-1].
566
566
567 # EOF
567 # EOF
568 if i == -1:
568 if i == -1:
569 self._rbuf = b''
569 self._rbuf = b''
570 return b''.join(chunks)
570 return b''.join(chunks)
571
571
572 i += 1
572 i += 1
573 self._rbuf = chunks[-1][i:]
573 self._rbuf = chunks[-1][i:]
574 chunks[-1] = chunks[-1][:i]
574 chunks[-1] = chunks[-1][:i]
575 return b''.join(chunks)
575 return b''.join(chunks)
576
576
577 def readlines(self, sizehint=0):
577 def readlines(self, sizehint=0):
578 total = 0
578 total = 0
579 list = []
579 list = []
580 while True:
580 while True:
581 line = self.readline()
581 line = self.readline()
582 if not line:
582 if not line:
583 break
583 break
584 list.append(line)
584 list.append(line)
585 total += len(line)
585 total += len(line)
586 if sizehint and total >= sizehint:
586 if sizehint and total >= sizehint:
587 break
587 break
588 return list
588 return list
589
589
590 def readinto(self, dest):
590 def readinto(self, dest):
591 if self._raw_readinto is None:
591 if self._raw_readinto is None:
592 res = self.read(len(dest))
592 res = self.read(len(dest))
593 if not res:
593 if not res:
594 return 0
594 return 0
595 dest[0 : len(res)] = res
595 dest[0 : len(res)] = res
596 return len(res)
596 return len(res)
597 total = len(dest)
597 total = len(dest)
598 have = len(self._rbuf)
598 have = len(self._rbuf)
599 if have >= total:
599 if have >= total:
600 dest[0:total] = self._rbuf[:total]
600 dest[0:total] = self._rbuf[:total]
601 self._rbuf = self._rbuf[total:]
601 self._rbuf = self._rbuf[total:]
602 return total
602 return total
603 mv = memoryview(dest)
603 mv = memoryview(dest)
604 got = self._raw_readinto(mv[have:total])
604 got = self._raw_readinto(mv[have:total])
605
605
606 self.receivedbytescount += got
606 self.receivedbytescount += got
607 self._connection.receivedbytescount += got
607 self._connection.receivedbytescount += got
608 try:
608 try:
609 self._handler.receivedbytescount += got
609 self._handler.receivedbytescount += got
610 except AttributeError:
610 except AttributeError:
611 pass
611 pass
612
612
613 dest[0:have] = self._rbuf
613 dest[0:have] = self._rbuf
614 got += len(self._rbuf)
614 got += len(self._rbuf)
615 self._rbuf = b''
615 self._rbuf = b''
616 return got
616 return got
617
617
618
618
619 def safesend(self, str):
619 def safesend(self, str):
620 """Send `str' to the server.
620 """Send `str' to the server.
621
621
622 Shamelessly ripped off from httplib to patch a bad behavior.
622 Shamelessly ripped off from httplib to patch a bad behavior.
623 """
623 """
624 # _broken_pipe_resp is an attribute we set in this function
624 # _broken_pipe_resp is an attribute we set in this function
625 # if the socket is closed while we're sending data but
625 # if the socket is closed while we're sending data but
626 # the server sent us a response before hanging up.
626 # the server sent us a response before hanging up.
627 # In that case, we want to pretend to send the rest of the
627 # In that case, we want to pretend to send the rest of the
628 # outgoing data, and then let the user use getresponse()
628 # outgoing data, and then let the user use getresponse()
629 # (which we wrap) to get this last response before
629 # (which we wrap) to get this last response before
630 # opening a new socket.
630 # opening a new socket.
631 if getattr(self, '_broken_pipe_resp', None) is not None:
631 if getattr(self, '_broken_pipe_resp', None) is not None:
632 return
632 return
633
633
634 if self.sock is None:
634 if self.sock is None:
635 if self.auto_open:
635 if self.auto_open:
636 self.connect()
636 self.connect()
637 else:
637 else:
638 raise httplib.NotConnected
638 raise httplib.NotConnected
639
639
640 # send the data to the server. if we get a broken pipe, then close
640 # send the data to the server. if we get a broken pipe, then close
641 # the socket. we want to reconnect when somebody tries to send again.
641 # the socket. we want to reconnect when somebody tries to send again.
642 #
642 #
643 # NOTE: we DO propagate the error, though, because we cannot simply
643 # NOTE: we DO propagate the error, though, because we cannot simply
644 # ignore the error... the caller will know if they can retry.
644 # ignore the error... the caller will know if they can retry.
645 if self.debuglevel > 0:
645 if self.debuglevel > 0:
646 print(b"send:", repr(str))
646 print(b"send:", repr(str))
647 try:
647 try:
648 blocksize = 8192
648 blocksize = 8192
649 read = getattr(str, 'read', None)
649 read = getattr(str, 'read', None)
650 if read is not None:
650 if read is not None:
651 if self.debuglevel > 0:
651 if self.debuglevel > 0:
652 print(b"sending a read()able")
652 print(b"sending a read()able")
653 data = read(blocksize)
653 data = read(blocksize)
654 while data:
654 while data:
655 self.sock.sendall(data)
655 self.sock.sendall(data)
656 self.sentbytescount += len(data)
656 self.sentbytescount += len(data)
657 data = read(blocksize)
657 data = read(blocksize)
658 else:
658 else:
659 self.sock.sendall(str)
659 self.sock.sendall(str)
660 self.sentbytescount += len(str)
660 self.sentbytescount += len(str)
661 except BrokenPipeError:
661 except BrokenPipeError:
662 if self._HTTPConnection__state == httplib._CS_REQ_SENT:
662 if self._HTTPConnection__state == httplib._CS_REQ_SENT:
663 self._broken_pipe_resp = None
663 self._broken_pipe_resp = None
664 self._broken_pipe_resp = self.getresponse()
664 self._broken_pipe_resp = self.getresponse()
665 reraise = False
665 reraise = False
666 else:
666 else:
667 reraise = True
667 reraise = True
668 self.close()
668 self.close()
669 if reraise:
669 if reraise:
670 raise
670 raise
671
671
672
672
673 def wrapgetresponse(cls):
673 def wrapgetresponse(cls):
674 """Wraps getresponse in cls with a broken-pipe sane version."""
674 """Wraps getresponse in cls with a broken-pipe sane version."""
675
675
676 def safegetresponse(self):
676 def safegetresponse(self):
677 # In safesend() we might set the _broken_pipe_resp
677 # In safesend() we might set the _broken_pipe_resp
678 # attribute, in which case the socket has already
678 # attribute, in which case the socket has already
679 # been closed and we just need to give them the response
679 # been closed and we just need to give them the response
680 # back. Otherwise, we use the normal response path.
680 # back. Otherwise, we use the normal response path.
681 r = getattr(self, '_broken_pipe_resp', None)
681 r = getattr(self, '_broken_pipe_resp', None)
682 if r is not None:
682 if r is not None:
683 return r
683 return r
684 return cls.getresponse(self)
684 return cls.getresponse(self)
685
685
686 safegetresponse.__doc__ = cls.getresponse.__doc__
686 safegetresponse.__doc__ = cls.getresponse.__doc__
687 return safegetresponse
687 return safegetresponse
688
688
689
689
690 class HTTPConnection(httplib.HTTPConnection):
690 class HTTPConnection(httplib.HTTPConnection):
691 # url.httpsconnection inherits from this. So when adding/removing
691 # url.httpsconnection inherits from this. So when adding/removing
692 # attributes, be sure to audit httpsconnection() for unintended
692 # attributes, be sure to audit httpsconnection() for unintended
693 # consequences.
693 # consequences.
694
694
695 # use the modified response class
695 # use the modified response class
696 response_class = HTTPResponse
696 response_class = HTTPResponse
697 send = safesend
697 send = safesend
698 getresponse = wrapgetresponse(httplib.HTTPConnection)
698 getresponse = wrapgetresponse(httplib.HTTPConnection)
699
699
700 def __init__(self, *args, **kwargs):
700 def __init__(self, *args, **kwargs):
701 httplib.HTTPConnection.__init__(self, *args, **kwargs)
701 httplib.HTTPConnection.__init__(self, *args, **kwargs)
702 self.sentbytescount = 0
702 self.sentbytescount = 0
703 self.receivedbytescount = 0
703 self.receivedbytescount = 0
704
704
705 def __repr__(self):
706 base = super(HTTPConnection, self).__repr__()
707 local = "(unconnected)"
708 s = self.sock
709 if s:
710 try:
711 local = "%s:%d" % s.getsockname()
712 except OSError:
713 pass # Likely not connected
714 return "<%s: %s <--> %s:%d>" % (base, local, self.host, self.port)
715
705
716
706 #########################################################################
717 #########################################################################
707 ##### TEST FUNCTIONS
718 ##### TEST FUNCTIONS
708 #########################################################################
719 #########################################################################
709
720
710
721
711 def continuity(url):
722 def continuity(url):
712 md5 = hashlib.md5
723 md5 = hashlib.md5
713 format = b'%25s: %s'
724 format = b'%25s: %s'
714
725
715 # first fetch the file with the normal http handler
726 # first fetch the file with the normal http handler
716 opener = urlreq.buildopener()
727 opener = urlreq.buildopener()
717 urlreq.installopener(opener)
728 urlreq.installopener(opener)
718 fo = urlreq.urlopen(url)
729 fo = urlreq.urlopen(url)
719 foo = fo.read()
730 foo = fo.read()
720 fo.close()
731 fo.close()
721 m = md5(foo)
732 m = md5(foo)
722 print(format % (b'normal urllib', hex(m.digest())))
733 print(format % (b'normal urllib', hex(m.digest())))
723
734
724 # now install the keepalive handler and try again
735 # now install the keepalive handler and try again
725 opener = urlreq.buildopener(HTTPHandler())
736 opener = urlreq.buildopener(HTTPHandler())
726 urlreq.installopener(opener)
737 urlreq.installopener(opener)
727
738
728 fo = urlreq.urlopen(url)
739 fo = urlreq.urlopen(url)
729 foo = fo.read()
740 foo = fo.read()
730 fo.close()
741 fo.close()
731 m = md5(foo)
742 m = md5(foo)
732 print(format % (b'keepalive read', hex(m.digest())))
743 print(format % (b'keepalive read', hex(m.digest())))
733
744
734 fo = urlreq.urlopen(url)
745 fo = urlreq.urlopen(url)
735 foo = b''
746 foo = b''
736 while True:
747 while True:
737 f = fo.readline()
748 f = fo.readline()
738 if f:
749 if f:
739 foo = foo + f
750 foo = foo + f
740 else:
751 else:
741 break
752 break
742 fo.close()
753 fo.close()
743 m = md5(foo)
754 m = md5(foo)
744 print(format % (b'keepalive readline', hex(m.digest())))
755 print(format % (b'keepalive readline', hex(m.digest())))
745
756
746
757
747 def comp(N, url):
758 def comp(N, url):
748 print(b' making %i connections to:\n %s' % (N, url))
759 print(b' making %i connections to:\n %s' % (N, url))
749
760
750 procutil.stdout.write(b' first using the normal urllib handlers')
761 procutil.stdout.write(b' first using the normal urllib handlers')
751 # first use normal opener
762 # first use normal opener
752 opener = urlreq.buildopener()
763 opener = urlreq.buildopener()
753 urlreq.installopener(opener)
764 urlreq.installopener(opener)
754 t1 = fetch(N, url)
765 t1 = fetch(N, url)
755 print(b' TIME: %.3f s' % t1)
766 print(b' TIME: %.3f s' % t1)
756
767
757 procutil.stdout.write(b' now using the keepalive handler ')
768 procutil.stdout.write(b' now using the keepalive handler ')
758 # now install the keepalive handler and try again
769 # now install the keepalive handler and try again
759 opener = urlreq.buildopener(HTTPHandler())
770 opener = urlreq.buildopener(HTTPHandler())
760 urlreq.installopener(opener)
771 urlreq.installopener(opener)
761 t2 = fetch(N, url)
772 t2 = fetch(N, url)
762 print(b' TIME: %.3f s' % t2)
773 print(b' TIME: %.3f s' % t2)
763 print(b' improvement factor: %.2f' % (t1 / t2))
774 print(b' improvement factor: %.2f' % (t1 / t2))
764
775
765
776
766 def fetch(N, url, delay=0):
777 def fetch(N, url, delay=0):
767 import time
778 import time
768
779
769 lens = []
780 lens = []
770 starttime = time.time()
781 starttime = time.time()
771 for i in range(N):
782 for i in range(N):
772 if delay and i > 0:
783 if delay and i > 0:
773 time.sleep(delay)
784 time.sleep(delay)
774 fo = urlreq.urlopen(url)
785 fo = urlreq.urlopen(url)
775 foo = fo.read()
786 foo = fo.read()
776 fo.close()
787 fo.close()
777 lens.append(len(foo))
788 lens.append(len(foo))
778 diff = time.time() - starttime
789 diff = time.time() - starttime
779
790
780 j = 0
791 j = 0
781 for i in lens[1:]:
792 for i in lens[1:]:
782 j = j + 1
793 j = j + 1
783 if not i == lens[0]:
794 if not i == lens[0]:
784 print(b"WARNING: inconsistent length on read %i: %i" % (j, i))
795 print(b"WARNING: inconsistent length on read %i: %i" % (j, i))
785
796
786 return diff
797 return diff
787
798
788
799
789 def test_timeout(url):
800 def test_timeout(url):
790 global DEBUG
801 global DEBUG
791 dbbackup = DEBUG
802 dbbackup = DEBUG
792
803
793 class FakeLogger:
804 class FakeLogger:
794 def debug(self, msg, *args):
805 def debug(self, msg, *args):
795 print(msg % args)
806 print(msg % args)
796
807
797 info = warning = error = debug
808 info = warning = error = debug
798
809
799 DEBUG = FakeLogger()
810 DEBUG = FakeLogger()
800 print(b" fetching the file to establish a connection")
811 print(b" fetching the file to establish a connection")
801 fo = urlreq.urlopen(url)
812 fo = urlreq.urlopen(url)
802 data1 = fo.read()
813 data1 = fo.read()
803 fo.close()
814 fo.close()
804
815
805 i = 20
816 i = 20
806 print(b" waiting %i seconds for the server to close the connection" % i)
817 print(b" waiting %i seconds for the server to close the connection" % i)
807 while i > 0:
818 while i > 0:
808 procutil.stdout.write(b'\r %2i' % i)
819 procutil.stdout.write(b'\r %2i' % i)
809 procutil.stdout.flush()
820 procutil.stdout.flush()
810 time.sleep(1)
821 time.sleep(1)
811 i -= 1
822 i -= 1
812 procutil.stderr.write(b'\r')
823 procutil.stderr.write(b'\r')
813
824
814 print(b" fetching the file a second time")
825 print(b" fetching the file a second time")
815 fo = urlreq.urlopen(url)
826 fo = urlreq.urlopen(url)
816 data2 = fo.read()
827 data2 = fo.read()
817 fo.close()
828 fo.close()
818
829
819 if data1 == data2:
830 if data1 == data2:
820 print(b' data are identical')
831 print(b' data are identical')
821 else:
832 else:
822 print(b' ERROR: DATA DIFFER')
833 print(b' ERROR: DATA DIFFER')
823
834
824 DEBUG = dbbackup
835 DEBUG = dbbackup
825
836
826
837
827 def test(url, N=10):
838 def test(url, N=10):
828 print(b"performing continuity test (making sure stuff isn't corrupted)")
839 print(b"performing continuity test (making sure stuff isn't corrupted)")
829 continuity(url)
840 continuity(url)
830 print(b'')
841 print(b'')
831 print(b"performing speed comparison")
842 print(b"performing speed comparison")
832 comp(N, url)
843 comp(N, url)
833 print(b'')
844 print(b'')
834 print(b"performing dropped-connection check")
845 print(b"performing dropped-connection check")
835 test_timeout(url)
846 test_timeout(url)
836
847
837
848
838 if __name__ == '__main__':
849 if __name__ == '__main__':
839 import time
850 import time
840
851
841 try:
852 try:
842 N = int(sys.argv[1])
853 N = int(sys.argv[1])
843 url = sys.argv[2]
854 url = sys.argv[2]
844 except (IndexError, ValueError):
855 except (IndexError, ValueError):
845 print(b"%s <integer> <url>" % sys.argv[0])
856 print(b"%s <integer> <url>" % sys.argv[0])
846 else:
857 else:
847 test(url, N)
858 test(url, N)
General Comments 0
You need to be logged in to leave comments. Login now