##// END OF EJS Templates
py3: stop subscripting socket.error...
Matt Harbison -
r40909:34835265 default
parent child Browse files
Show More
@@ -1,373 +1,373 b''
1 # hgweb/server.py - The standalone hg web server.
1 # hgweb/server.py - The standalone hg web server.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import os
12 import os
13 import socket
13 import socket
14 import sys
14 import sys
15 import traceback
15 import traceback
16 import wsgiref.validate
16 import wsgiref.validate
17
17
18 from ..i18n import _
18 from ..i18n import _
19
19
20 from .. import (
20 from .. import (
21 encoding,
21 encoding,
22 error,
22 error,
23 pycompat,
23 pycompat,
24 util,
24 util,
25 )
25 )
26
26
27 httpservermod = util.httpserver
27 httpservermod = util.httpserver
28 socketserver = util.socketserver
28 socketserver = util.socketserver
29 urlerr = util.urlerr
29 urlerr = util.urlerr
30 urlreq = util.urlreq
30 urlreq = util.urlreq
31
31
32 from . import (
32 from . import (
33 common,
33 common,
34 )
34 )
35
35
36 def _splitURI(uri):
36 def _splitURI(uri):
37 """Return path and query that has been split from uri
37 """Return path and query that has been split from uri
38
38
39 Just like CGI environment, the path is unquoted, the query is
39 Just like CGI environment, the path is unquoted, the query is
40 not.
40 not.
41 """
41 """
42 if r'?' in uri:
42 if r'?' in uri:
43 path, query = uri.split(r'?', 1)
43 path, query = uri.split(r'?', 1)
44 else:
44 else:
45 path, query = uri, r''
45 path, query = uri, r''
46 return urlreq.unquote(path), query
46 return urlreq.unquote(path), query
47
47
48 class _error_logger(object):
48 class _error_logger(object):
49 def __init__(self, handler):
49 def __init__(self, handler):
50 self.handler = handler
50 self.handler = handler
51 def flush(self):
51 def flush(self):
52 pass
52 pass
53 def write(self, str):
53 def write(self, str):
54 self.writelines(str.split('\n'))
54 self.writelines(str.split('\n'))
55 def writelines(self, seq):
55 def writelines(self, seq):
56 for msg in seq:
56 for msg in seq:
57 self.handler.log_error("HG error: %s", msg)
57 self.handler.log_error("HG error: %s", msg)
58
58
59 class _httprequesthandler(httpservermod.basehttprequesthandler):
59 class _httprequesthandler(httpservermod.basehttprequesthandler):
60
60
61 url_scheme = 'http'
61 url_scheme = 'http'
62
62
63 @staticmethod
63 @staticmethod
64 def preparehttpserver(httpserver, ui):
64 def preparehttpserver(httpserver, ui):
65 """Prepare .socket of new HTTPServer instance"""
65 """Prepare .socket of new HTTPServer instance"""
66
66
67 def __init__(self, *args, **kargs):
67 def __init__(self, *args, **kargs):
68 self.protocol_version = r'HTTP/1.1'
68 self.protocol_version = r'HTTP/1.1'
69 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
69 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
70
70
71 def _log_any(self, fp, format, *args):
71 def _log_any(self, fp, format, *args):
72 fp.write(pycompat.sysbytes(
72 fp.write(pycompat.sysbytes(
73 r"%s - - [%s] %s" % (self.client_address[0],
73 r"%s - - [%s] %s" % (self.client_address[0],
74 self.log_date_time_string(),
74 self.log_date_time_string(),
75 format % args)) + '\n')
75 format % args)) + '\n')
76 fp.flush()
76 fp.flush()
77
77
78 def log_error(self, format, *args):
78 def log_error(self, format, *args):
79 self._log_any(self.server.errorlog, format, *args)
79 self._log_any(self.server.errorlog, format, *args)
80
80
81 def log_message(self, format, *args):
81 def log_message(self, format, *args):
82 self._log_any(self.server.accesslog, format, *args)
82 self._log_any(self.server.accesslog, format, *args)
83
83
84 def log_request(self, code=r'-', size=r'-'):
84 def log_request(self, code=r'-', size=r'-'):
85 xheaders = []
85 xheaders = []
86 if util.safehasattr(self, 'headers'):
86 if util.safehasattr(self, 'headers'):
87 xheaders = [h for h in self.headers.items()
87 xheaders = [h for h in self.headers.items()
88 if h[0].startswith(r'x-')]
88 if h[0].startswith(r'x-')]
89 self.log_message(r'"%s" %s %s%s',
89 self.log_message(r'"%s" %s %s%s',
90 self.requestline, str(code), str(size),
90 self.requestline, str(code), str(size),
91 r''.join([r' %s:%s' % h for h in sorted(xheaders)]))
91 r''.join([r' %s:%s' % h for h in sorted(xheaders)]))
92
92
93 def do_write(self):
93 def do_write(self):
94 try:
94 try:
95 self.do_hgweb()
95 self.do_hgweb()
96 except socket.error as inst:
96 except socket.error as inst:
97 if inst[0] != errno.EPIPE:
97 if inst.errno != errno.EPIPE:
98 raise
98 raise
99
99
100 def do_POST(self):
100 def do_POST(self):
101 try:
101 try:
102 self.do_write()
102 self.do_write()
103 except Exception:
103 except Exception:
104 self._start_response(r"500 Internal Server Error", [])
104 self._start_response(r"500 Internal Server Error", [])
105 self._write(b"Internal Server Error")
105 self._write(b"Internal Server Error")
106 self._done()
106 self._done()
107 tb = r"".join(traceback.format_exception(*sys.exc_info()))
107 tb = r"".join(traceback.format_exception(*sys.exc_info()))
108 # We need a native-string newline to poke in the log
108 # We need a native-string newline to poke in the log
109 # message, because we won't get a newline when using an
109 # message, because we won't get a newline when using an
110 # r-string. This is the easy way out.
110 # r-string. This is the easy way out.
111 newline = chr(10)
111 newline = chr(10)
112 self.log_error(r"Exception happened during processing "
112 self.log_error(r"Exception happened during processing "
113 r"request '%s':%s%s", self.path, newline, tb)
113 r"request '%s':%s%s", self.path, newline, tb)
114
114
115 def do_PUT(self):
115 def do_PUT(self):
116 self.do_POST()
116 self.do_POST()
117
117
118 def do_GET(self):
118 def do_GET(self):
119 self.do_POST()
119 self.do_POST()
120
120
121 def do_hgweb(self):
121 def do_hgweb(self):
122 self.sent_headers = False
122 self.sent_headers = False
123 path, query = _splitURI(self.path)
123 path, query = _splitURI(self.path)
124
124
125 # Ensure the slicing of path below is valid
125 # Ensure the slicing of path below is valid
126 if (path != self.server.prefix
126 if (path != self.server.prefix
127 and not path.startswith(self.server.prefix + b'/')):
127 and not path.startswith(self.server.prefix + b'/')):
128 self._start_response(pycompat.strurl(common.statusmessage(404)),
128 self._start_response(pycompat.strurl(common.statusmessage(404)),
129 [])
129 [])
130 self._write(b"Not Found")
130 self._write(b"Not Found")
131 self._done()
131 self._done()
132 return
132 return
133
133
134 env = {}
134 env = {}
135 env[r'GATEWAY_INTERFACE'] = r'CGI/1.1'
135 env[r'GATEWAY_INTERFACE'] = r'CGI/1.1'
136 env[r'REQUEST_METHOD'] = self.command
136 env[r'REQUEST_METHOD'] = self.command
137 env[r'SERVER_NAME'] = self.server.server_name
137 env[r'SERVER_NAME'] = self.server.server_name
138 env[r'SERVER_PORT'] = str(self.server.server_port)
138 env[r'SERVER_PORT'] = str(self.server.server_port)
139 env[r'REQUEST_URI'] = self.path
139 env[r'REQUEST_URI'] = self.path
140 env[r'SCRIPT_NAME'] = pycompat.sysstr(self.server.prefix)
140 env[r'SCRIPT_NAME'] = pycompat.sysstr(self.server.prefix)
141 env[r'PATH_INFO'] = pycompat.sysstr(path[len(self.server.prefix):])
141 env[r'PATH_INFO'] = pycompat.sysstr(path[len(self.server.prefix):])
142 env[r'REMOTE_HOST'] = self.client_address[0]
142 env[r'REMOTE_HOST'] = self.client_address[0]
143 env[r'REMOTE_ADDR'] = self.client_address[0]
143 env[r'REMOTE_ADDR'] = self.client_address[0]
144 env[r'QUERY_STRING'] = query or r''
144 env[r'QUERY_STRING'] = query or r''
145
145
146 if pycompat.ispy3:
146 if pycompat.ispy3:
147 if self.headers.get_content_type() is None:
147 if self.headers.get_content_type() is None:
148 env[r'CONTENT_TYPE'] = self.headers.get_default_type()
148 env[r'CONTENT_TYPE'] = self.headers.get_default_type()
149 else:
149 else:
150 env[r'CONTENT_TYPE'] = self.headers.get_content_type()
150 env[r'CONTENT_TYPE'] = self.headers.get_content_type()
151 length = self.headers.get(r'content-length')
151 length = self.headers.get(r'content-length')
152 else:
152 else:
153 if self.headers.typeheader is None:
153 if self.headers.typeheader is None:
154 env[r'CONTENT_TYPE'] = self.headers.type
154 env[r'CONTENT_TYPE'] = self.headers.type
155 else:
155 else:
156 env[r'CONTENT_TYPE'] = self.headers.typeheader
156 env[r'CONTENT_TYPE'] = self.headers.typeheader
157 length = self.headers.getheader(r'content-length')
157 length = self.headers.getheader(r'content-length')
158 if length:
158 if length:
159 env[r'CONTENT_LENGTH'] = length
159 env[r'CONTENT_LENGTH'] = length
160 for header in [h for h in self.headers.keys()
160 for header in [h for h in self.headers.keys()
161 if h not in (r'content-type', r'content-length')]:
161 if h not in (r'content-type', r'content-length')]:
162 hkey = r'HTTP_' + header.replace(r'-', r'_').upper()
162 hkey = r'HTTP_' + header.replace(r'-', r'_').upper()
163 hval = self.headers.get(header)
163 hval = self.headers.get(header)
164 hval = hval.replace(r'\n', r'').strip()
164 hval = hval.replace(r'\n', r'').strip()
165 if hval:
165 if hval:
166 env[hkey] = hval
166 env[hkey] = hval
167 env[r'SERVER_PROTOCOL'] = self.request_version
167 env[r'SERVER_PROTOCOL'] = self.request_version
168 env[r'wsgi.version'] = (1, 0)
168 env[r'wsgi.version'] = (1, 0)
169 env[r'wsgi.url_scheme'] = pycompat.sysstr(self.url_scheme)
169 env[r'wsgi.url_scheme'] = pycompat.sysstr(self.url_scheme)
170 if env.get(r'HTTP_EXPECT', '').lower() == '100-continue':
170 if env.get(r'HTTP_EXPECT', '').lower() == '100-continue':
171 self.rfile = common.continuereader(self.rfile, self.wfile.write)
171 self.rfile = common.continuereader(self.rfile, self.wfile.write)
172
172
173 env[r'wsgi.input'] = self.rfile
173 env[r'wsgi.input'] = self.rfile
174 env[r'wsgi.errors'] = _error_logger(self)
174 env[r'wsgi.errors'] = _error_logger(self)
175 env[r'wsgi.multithread'] = isinstance(self.server,
175 env[r'wsgi.multithread'] = isinstance(self.server,
176 socketserver.ThreadingMixIn)
176 socketserver.ThreadingMixIn)
177 if util.safehasattr(socketserver, 'ForkingMixIn'):
177 if util.safehasattr(socketserver, 'ForkingMixIn'):
178 env[r'wsgi.multiprocess'] = isinstance(self.server,
178 env[r'wsgi.multiprocess'] = isinstance(self.server,
179 socketserver.ForkingMixIn)
179 socketserver.ForkingMixIn)
180 else:
180 else:
181 env[r'wsgi.multiprocess'] = False
181 env[r'wsgi.multiprocess'] = False
182
182
183 env[r'wsgi.run_once'] = 0
183 env[r'wsgi.run_once'] = 0
184
184
185 wsgiref.validate.check_environ(env)
185 wsgiref.validate.check_environ(env)
186
186
187 self.saved_status = None
187 self.saved_status = None
188 self.saved_headers = []
188 self.saved_headers = []
189 self.length = None
189 self.length = None
190 self._chunked = None
190 self._chunked = None
191 for chunk in self.server.application(env, self._start_response):
191 for chunk in self.server.application(env, self._start_response):
192 self._write(chunk)
192 self._write(chunk)
193 if not self.sent_headers:
193 if not self.sent_headers:
194 self.send_headers()
194 self.send_headers()
195 self._done()
195 self._done()
196
196
197 def send_headers(self):
197 def send_headers(self):
198 if not self.saved_status:
198 if not self.saved_status:
199 raise AssertionError("Sending headers before "
199 raise AssertionError("Sending headers before "
200 "start_response() called")
200 "start_response() called")
201 saved_status = self.saved_status.split(None, 1)
201 saved_status = self.saved_status.split(None, 1)
202 saved_status[0] = int(saved_status[0])
202 saved_status[0] = int(saved_status[0])
203 self.send_response(*saved_status)
203 self.send_response(*saved_status)
204 self.length = None
204 self.length = None
205 self._chunked = False
205 self._chunked = False
206 for h in self.saved_headers:
206 for h in self.saved_headers:
207 self.send_header(*h)
207 self.send_header(*h)
208 if h[0].lower() == r'content-length':
208 if h[0].lower() == r'content-length':
209 self.length = int(h[1])
209 self.length = int(h[1])
210 if (self.length is None and
210 if (self.length is None and
211 saved_status[0] != common.HTTP_NOT_MODIFIED):
211 saved_status[0] != common.HTTP_NOT_MODIFIED):
212 self._chunked = (not self.close_connection and
212 self._chunked = (not self.close_connection and
213 self.request_version == r'HTTP/1.1')
213 self.request_version == r'HTTP/1.1')
214 if self._chunked:
214 if self._chunked:
215 self.send_header(r'Transfer-Encoding', r'chunked')
215 self.send_header(r'Transfer-Encoding', r'chunked')
216 else:
216 else:
217 self.send_header(r'Connection', r'close')
217 self.send_header(r'Connection', r'close')
218 self.end_headers()
218 self.end_headers()
219 self.sent_headers = True
219 self.sent_headers = True
220
220
221 def _start_response(self, http_status, headers, exc_info=None):
221 def _start_response(self, http_status, headers, exc_info=None):
222 assert isinstance(http_status, str)
222 assert isinstance(http_status, str)
223 code, msg = http_status.split(None, 1)
223 code, msg = http_status.split(None, 1)
224 code = int(code)
224 code = int(code)
225 self.saved_status = http_status
225 self.saved_status = http_status
226 bad_headers = (r'connection', r'transfer-encoding')
226 bad_headers = (r'connection', r'transfer-encoding')
227 self.saved_headers = [h for h in headers
227 self.saved_headers = [h for h in headers
228 if h[0].lower() not in bad_headers]
228 if h[0].lower() not in bad_headers]
229 return self._write
229 return self._write
230
230
231 def _write(self, data):
231 def _write(self, data):
232 if not self.saved_status:
232 if not self.saved_status:
233 raise AssertionError("data written before start_response() called")
233 raise AssertionError("data written before start_response() called")
234 elif not self.sent_headers:
234 elif not self.sent_headers:
235 self.send_headers()
235 self.send_headers()
236 if self.length is not None:
236 if self.length is not None:
237 if len(data) > self.length:
237 if len(data) > self.length:
238 raise AssertionError("Content-length header sent, but more "
238 raise AssertionError("Content-length header sent, but more "
239 "bytes than specified are being written.")
239 "bytes than specified are being written.")
240 self.length = self.length - len(data)
240 self.length = self.length - len(data)
241 elif self._chunked and data:
241 elif self._chunked and data:
242 data = '%x\r\n%s\r\n' % (len(data), data)
242 data = '%x\r\n%s\r\n' % (len(data), data)
243 self.wfile.write(data)
243 self.wfile.write(data)
244 self.wfile.flush()
244 self.wfile.flush()
245
245
246 def _done(self):
246 def _done(self):
247 if self._chunked:
247 if self._chunked:
248 self.wfile.write('0\r\n\r\n')
248 self.wfile.write('0\r\n\r\n')
249 self.wfile.flush()
249 self.wfile.flush()
250
250
251 def version_string(self):
251 def version_string(self):
252 if self.server.serverheader:
252 if self.server.serverheader:
253 return encoding.strfromlocal(self.server.serverheader)
253 return encoding.strfromlocal(self.server.serverheader)
254 return httpservermod.basehttprequesthandler.version_string(self)
254 return httpservermod.basehttprequesthandler.version_string(self)
255
255
256 class _httprequesthandlerssl(_httprequesthandler):
256 class _httprequesthandlerssl(_httprequesthandler):
257 """HTTPS handler based on Python's ssl module"""
257 """HTTPS handler based on Python's ssl module"""
258
258
259 url_scheme = 'https'
259 url_scheme = 'https'
260
260
261 @staticmethod
261 @staticmethod
262 def preparehttpserver(httpserver, ui):
262 def preparehttpserver(httpserver, ui):
263 try:
263 try:
264 from .. import sslutil
264 from .. import sslutil
265 sslutil.modernssl
265 sslutil.modernssl
266 except ImportError:
266 except ImportError:
267 raise error.Abort(_("SSL support is unavailable"))
267 raise error.Abort(_("SSL support is unavailable"))
268
268
269 certfile = ui.config('web', 'certificate')
269 certfile = ui.config('web', 'certificate')
270
270
271 # These config options are currently only meant for testing. Use
271 # These config options are currently only meant for testing. Use
272 # at your own risk.
272 # at your own risk.
273 cafile = ui.config('devel', 'servercafile')
273 cafile = ui.config('devel', 'servercafile')
274 reqcert = ui.configbool('devel', 'serverrequirecert')
274 reqcert = ui.configbool('devel', 'serverrequirecert')
275
275
276 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
276 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
277 ui,
277 ui,
278 certfile=certfile,
278 certfile=certfile,
279 cafile=cafile,
279 cafile=cafile,
280 requireclientcert=reqcert)
280 requireclientcert=reqcert)
281
281
282 def setup(self):
282 def setup(self):
283 self.connection = self.request
283 self.connection = self.request
284 self.rfile = self.request.makefile(r"rb", self.rbufsize)
284 self.rfile = self.request.makefile(r"rb", self.rbufsize)
285 self.wfile = self.request.makefile(r"wb", self.wbufsize)
285 self.wfile = self.request.makefile(r"wb", self.wbufsize)
286
286
287 try:
287 try:
288 import threading
288 import threading
289 threading.activeCount() # silence pyflakes and bypass demandimport
289 threading.activeCount() # silence pyflakes and bypass demandimport
290 _mixin = socketserver.ThreadingMixIn
290 _mixin = socketserver.ThreadingMixIn
291 except ImportError:
291 except ImportError:
292 if util.safehasattr(os, "fork"):
292 if util.safehasattr(os, "fork"):
293 _mixin = socketserver.ForkingMixIn
293 _mixin = socketserver.ForkingMixIn
294 else:
294 else:
295 class _mixin(object):
295 class _mixin(object):
296 pass
296 pass
297
297
298 def openlog(opt, default):
298 def openlog(opt, default):
299 if opt and opt != '-':
299 if opt and opt != '-':
300 return open(opt, 'ab')
300 return open(opt, 'ab')
301 return default
301 return default
302
302
303 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
303 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
304
304
305 # SO_REUSEADDR has broken semantics on windows
305 # SO_REUSEADDR has broken semantics on windows
306 if pycompat.iswindows:
306 if pycompat.iswindows:
307 allow_reuse_address = 0
307 allow_reuse_address = 0
308
308
309 def __init__(self, ui, app, addr, handler, **kwargs):
309 def __init__(self, ui, app, addr, handler, **kwargs):
310 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
310 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
311 self.daemon_threads = True
311 self.daemon_threads = True
312 self.application = app
312 self.application = app
313
313
314 handler.preparehttpserver(self, ui)
314 handler.preparehttpserver(self, ui)
315
315
316 prefix = ui.config('web', 'prefix')
316 prefix = ui.config('web', 'prefix')
317 if prefix:
317 if prefix:
318 prefix = '/' + prefix.strip('/')
318 prefix = '/' + prefix.strip('/')
319 self.prefix = prefix
319 self.prefix = prefix
320
320
321 alog = openlog(ui.config('web', 'accesslog'), ui.fout)
321 alog = openlog(ui.config('web', 'accesslog'), ui.fout)
322 elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
322 elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
323 self.accesslog = alog
323 self.accesslog = alog
324 self.errorlog = elog
324 self.errorlog = elog
325
325
326 self.addr, self.port = self.socket.getsockname()[0:2]
326 self.addr, self.port = self.socket.getsockname()[0:2]
327 self.fqaddr = socket.getfqdn(addr[0])
327 self.fqaddr = socket.getfqdn(addr[0])
328
328
329 self.serverheader = ui.config('web', 'server-header')
329 self.serverheader = ui.config('web', 'server-header')
330
330
331 class IPv6HTTPServer(MercurialHTTPServer):
331 class IPv6HTTPServer(MercurialHTTPServer):
332 address_family = getattr(socket, 'AF_INET6', None)
332 address_family = getattr(socket, 'AF_INET6', None)
333 def __init__(self, *args, **kwargs):
333 def __init__(self, *args, **kwargs):
334 if self.address_family is None:
334 if self.address_family is None:
335 raise error.RepoError(_('IPv6 is not available on this system'))
335 raise error.RepoError(_('IPv6 is not available on this system'))
336 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
336 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
337
337
338 def create_server(ui, app):
338 def create_server(ui, app):
339
339
340 if ui.config('web', 'certificate'):
340 if ui.config('web', 'certificate'):
341 handler = _httprequesthandlerssl
341 handler = _httprequesthandlerssl
342 else:
342 else:
343 handler = _httprequesthandler
343 handler = _httprequesthandler
344
344
345 if ui.configbool('web', 'ipv6'):
345 if ui.configbool('web', 'ipv6'):
346 cls = IPv6HTTPServer
346 cls = IPv6HTTPServer
347 else:
347 else:
348 cls = MercurialHTTPServer
348 cls = MercurialHTTPServer
349
349
350 # ugly hack due to python issue5853 (for threaded use)
350 # ugly hack due to python issue5853 (for threaded use)
351 try:
351 try:
352 import mimetypes
352 import mimetypes
353 mimetypes.init()
353 mimetypes.init()
354 except UnicodeDecodeError:
354 except UnicodeDecodeError:
355 # Python 2.x's mimetypes module attempts to decode strings
355 # Python 2.x's mimetypes module attempts to decode strings
356 # from Windows' ANSI APIs as ascii (fail), then re-encode them
356 # from Windows' ANSI APIs as ascii (fail), then re-encode them
357 # as ascii (clown fail), because the default Python Unicode
357 # as ascii (clown fail), because the default Python Unicode
358 # codec is hardcoded as ascii.
358 # codec is hardcoded as ascii.
359
359
360 sys.argv # unwrap demand-loader so that reload() works
360 sys.argv # unwrap demand-loader so that reload() works
361 reload(sys) # resurrect sys.setdefaultencoding()
361 reload(sys) # resurrect sys.setdefaultencoding()
362 oldenc = sys.getdefaultencoding()
362 oldenc = sys.getdefaultencoding()
363 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
363 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
364 mimetypes.init()
364 mimetypes.init()
365 sys.setdefaultencoding(oldenc)
365 sys.setdefaultencoding(oldenc)
366
366
367 address = ui.config('web', 'address')
367 address = ui.config('web', 'address')
368 port = util.getport(ui.config('web', 'port'))
368 port = util.getport(ui.config('web', 'port'))
369 try:
369 try:
370 return cls(ui, app, (address, port), handler)
370 return cls(ui, app, (address, port), handler)
371 except socket.error as inst:
371 except socket.error as inst:
372 raise error.Abort(_("cannot start server at '%s:%d': %s")
372 raise error.Abort(_("cannot start server at '%s:%d': %s")
373 % (address, port, encoding.strtolocal(inst.args[1])))
373 % (address, port, encoding.strtolocal(inst.args[1])))
@@ -1,810 +1,810 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 from __future__ import absolute_import, print_function
85 from __future__ import absolute_import, print_function
86
86
87 import errno
87 import errno
88 import hashlib
88 import hashlib
89 import socket
89 import socket
90 import sys
90 import sys
91 import threading
91 import threading
92
92
93 from .i18n import _
93 from .i18n import _
94 from . import (
94 from . import (
95 node,
95 node,
96 pycompat,
96 pycompat,
97 urllibcompat,
97 urllibcompat,
98 util,
98 util,
99 )
99 )
100 from .utils import (
100 from .utils import (
101 procutil,
101 procutil,
102 )
102 )
103
103
104 httplib = util.httplib
104 httplib = util.httplib
105 urlerr = util.urlerr
105 urlerr = util.urlerr
106 urlreq = util.urlreq
106 urlreq = util.urlreq
107
107
108 DEBUG = None
108 DEBUG = None
109
109
110 class ConnectionManager(object):
110 class ConnectionManager(object):
111 """
111 """
112 The connection manager must be able to:
112 The connection manager must be able to:
113 * keep track of all existing
113 * keep track of all existing
114 """
114 """
115 def __init__(self):
115 def __init__(self):
116 self._lock = threading.Lock()
116 self._lock = threading.Lock()
117 self._hostmap = {} # map hosts to a list of connections
117 self._hostmap = {} # map hosts to a list of connections
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 if host not in self._hostmap:
124 if host not in self._hostmap:
125 self._hostmap[host] = []
125 self._hostmap[host] = []
126 self._hostmap[host].append(connection)
126 self._hostmap[host].append(connection)
127 self._connmap[connection] = host
127 self._connmap[connection] = host
128 self._readymap[connection] = ready
128 self._readymap[connection] = ready
129 finally:
129 finally:
130 self._lock.release()
130 self._lock.release()
131
131
132 def remove(self, connection):
132 def remove(self, connection):
133 self._lock.acquire()
133 self._lock.acquire()
134 try:
134 try:
135 try:
135 try:
136 host = self._connmap[connection]
136 host = self._connmap[connection]
137 except KeyError:
137 except KeyError:
138 pass
138 pass
139 else:
139 else:
140 del self._connmap[connection]
140 del self._connmap[connection]
141 del self._readymap[connection]
141 del self._readymap[connection]
142 self._hostmap[host].remove(connection)
142 self._hostmap[host].remove(connection)
143 if not self._hostmap[host]:
143 if not self._hostmap[host]:
144 del self._hostmap[host]
144 del self._hostmap[host]
145 finally:
145 finally:
146 self._lock.release()
146 self._lock.release()
147
147
148 def set_ready(self, connection, ready):
148 def set_ready(self, connection, ready):
149 try:
149 try:
150 self._readymap[connection] = ready
150 self._readymap[connection] = ready
151 except KeyError:
151 except KeyError:
152 pass
152 pass
153
153
154 def get_ready_conn(self, host):
154 def get_ready_conn(self, host):
155 conn = None
155 conn = None
156 self._lock.acquire()
156 self._lock.acquire()
157 try:
157 try:
158 if host in self._hostmap:
158 if host in self._hostmap:
159 for c in self._hostmap[host]:
159 for c in self._hostmap[host]:
160 if self._readymap[c]:
160 if self._readymap[c]:
161 self._readymap[c] = 0
161 self._readymap[c] = 0
162 conn = c
162 conn = c
163 break
163 break
164 finally:
164 finally:
165 self._lock.release()
165 self._lock.release()
166 return conn
166 return conn
167
167
168 def get_all(self, host=None):
168 def get_all(self, host=None):
169 if host:
169 if host:
170 return list(self._hostmap.get(host, []))
170 return list(self._hostmap.get(host, []))
171 else:
171 else:
172 return dict(self._hostmap)
172 return dict(self._hostmap)
173
173
174 class KeepAliveHandler(object):
174 class KeepAliveHandler(object):
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().iteritems():
197 for host, conns in self._cm.get_all().iteritems():
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, 1)
205 self._cm.set_ready(connection, 1)
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('no host given')
219 raise urlerr.urlerror('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("creating new connection to %s (%d)",
240 DEBUG.info("creating new connection to %s (%d)",
241 host, id(h))
241 host, id(h))
242 self._cm.add(host, h, 0)
242 self._cm.add(host, h, 0)
243 self._start_transaction(h, req)
243 self._start_transaction(h, req)
244 r = h.getresponse()
244 r = h.getresponse()
245 # The string form of BadStatusLine is the status line. Add some context
245 # The string form of BadStatusLine is the status line. Add some context
246 # to make the error message slightly more useful.
246 # to make the error message slightly more useful.
247 except httplib.BadStatusLine as err:
247 except httplib.BadStatusLine as err:
248 raise urlerr.urlerror(
248 raise urlerr.urlerror(
249 _('bad HTTP status line: %s') % pycompat.sysbytes(err.line))
249 _('bad HTTP status line: %s') % pycompat.sysbytes(err.line))
250 except (socket.error, httplib.HTTPException) as err:
250 except (socket.error, httplib.HTTPException) as err:
251 raise urlerr.urlerror(err)
251 raise urlerr.urlerror(err)
252
252
253 # If not a persistent connection, don't try to reuse it. Look
253 # If not a persistent connection, don't try to reuse it. Look
254 # for this using getattr() since vcr doesn't define this
254 # for this using getattr() since vcr doesn't define this
255 # attribute, and in that case always close the connection.
255 # attribute, and in that case always close the connection.
256 if getattr(r, r'will_close', True):
256 if getattr(r, r'will_close', True):
257 self._cm.remove(h)
257 self._cm.remove(h)
258
258
259 if DEBUG:
259 if DEBUG:
260 DEBUG.info("STATUS: %s, %s", r.status, r.reason)
260 DEBUG.info("STATUS: %s, %s", r.status, r.reason)
261 r._handler = self
261 r._handler = self
262 r._host = host
262 r._host = host
263 r._url = req.get_full_url()
263 r._url = req.get_full_url()
264 r._connection = h
264 r._connection = h
265 r.code = r.status
265 r.code = r.status
266 r.headers = r.msg
266 r.headers = r.msg
267 r.msg = r.reason
267 r.msg = r.reason
268
268
269 return r
269 return r
270
270
271 def _reuse_connection(self, h, req, host):
271 def _reuse_connection(self, h, req, host):
272 """start the transaction with a re-used connection
272 """start the transaction with a re-used connection
273 return a response object (r) upon success or None on failure.
273 return a response object (r) upon success or None on failure.
274 This DOES not close or remove bad connections in cases where
274 This DOES not close or remove bad connections in cases where
275 it returns. However, if an unexpected exception occurs, it
275 it returns. However, if an unexpected exception occurs, it
276 will close and remove the connection before re-raising.
276 will close and remove the connection before re-raising.
277 """
277 """
278 try:
278 try:
279 self._start_transaction(h, req)
279 self._start_transaction(h, req)
280 r = h.getresponse()
280 r = h.getresponse()
281 # note: just because we got something back doesn't mean it
281 # note: just because we got something back doesn't mean it
282 # worked. We'll check the version below, too.
282 # worked. We'll check the version below, too.
283 except (socket.error, httplib.HTTPException):
283 except (socket.error, httplib.HTTPException):
284 r = None
284 r = None
285 except: # re-raises
285 except: # re-raises
286 # adding this block just in case we've missed
286 # adding this block just in case we've missed
287 # something we will still raise the exception, but
287 # something we will still raise the exception, but
288 # lets try and close the connection and remove it
288 # lets try and close the connection and remove it
289 # first. We previously got into a nasty loop
289 # first. We previously got into a nasty loop
290 # where an exception was uncaught, and so the
290 # where an exception was uncaught, and so the
291 # connection stayed open. On the next try, the
291 # connection stayed open. On the next try, the
292 # same exception was raised, etc. The trade-off is
292 # same exception was raised, etc. The trade-off is
293 # that it's now possible this call will raise
293 # that it's now possible this call will raise
294 # a DIFFERENT exception
294 # a DIFFERENT exception
295 if DEBUG:
295 if DEBUG:
296 DEBUG.error("unexpected exception - closing "
296 DEBUG.error("unexpected exception - closing "
297 "connection to %s (%d)", host, id(h))
297 "connection to %s (%d)", host, id(h))
298 self._cm.remove(h)
298 self._cm.remove(h)
299 h.close()
299 h.close()
300 raise
300 raise
301
301
302 if r is None or r.version == 9:
302 if r is None or r.version == 9:
303 # httplib falls back to assuming HTTP 0.9 if it gets a
303 # httplib falls back to assuming HTTP 0.9 if it gets a
304 # bad header back. This is most likely to happen if
304 # bad header back. This is most likely to happen if
305 # the socket has been closed by the server since we
305 # the socket has been closed by the server since we
306 # last used the connection.
306 # last used the connection.
307 if DEBUG:
307 if DEBUG:
308 DEBUG.info("failed to re-use connection to %s (%d)",
308 DEBUG.info("failed to re-use connection to %s (%d)",
309 host, id(h))
309 host, id(h))
310 r = None
310 r = None
311 else:
311 else:
312 if DEBUG:
312 if DEBUG:
313 DEBUG.info("re-using connection to %s (%d)", host, id(h))
313 DEBUG.info("re-using connection to %s (%d)", host, id(h))
314
314
315 return r
315 return r
316
316
317 def _start_transaction(self, h, req):
317 def _start_transaction(self, h, req):
318 oldbytescount = getattr(h, 'sentbytescount', 0)
318 oldbytescount = getattr(h, 'sentbytescount', 0)
319
319
320 # What follows mostly reimplements HTTPConnection.request()
320 # What follows mostly reimplements HTTPConnection.request()
321 # except it adds self.parent.addheaders in the mix and sends headers
321 # except it adds self.parent.addheaders in the mix and sends headers
322 # in a deterministic order (to make testing easier).
322 # in a deterministic order (to make testing easier).
323 headers = util.sortdict(self.parent.addheaders)
323 headers = util.sortdict(self.parent.addheaders)
324 headers.update(sorted(req.headers.items()))
324 headers.update(sorted(req.headers.items()))
325 headers.update(sorted(req.unredirected_hdrs.items()))
325 headers.update(sorted(req.unredirected_hdrs.items()))
326 headers = util.sortdict((n.lower(), v) for n, v in headers.items())
326 headers = util.sortdict((n.lower(), v) for n, v in headers.items())
327 skipheaders = {}
327 skipheaders = {}
328 for n in (r'host', r'accept-encoding'):
328 for n in (r'host', r'accept-encoding'):
329 if n in headers:
329 if n in headers:
330 skipheaders[r'skip_' + n.replace(r'-', r'_')] = 1
330 skipheaders[r'skip_' + n.replace(r'-', r'_')] = 1
331 try:
331 try:
332 if urllibcompat.hasdata(req):
332 if urllibcompat.hasdata(req):
333 data = urllibcompat.getdata(req)
333 data = urllibcompat.getdata(req)
334 h.putrequest(
334 h.putrequest(
335 req.get_method(), urllibcompat.getselector(req),
335 req.get_method(), urllibcompat.getselector(req),
336 **skipheaders)
336 **skipheaders)
337 if r'content-type' not in headers:
337 if r'content-type' not in headers:
338 h.putheader(r'Content-type',
338 h.putheader(r'Content-type',
339 r'application/x-www-form-urlencoded')
339 r'application/x-www-form-urlencoded')
340 if r'content-length' not in headers:
340 if r'content-length' not in headers:
341 h.putheader(r'Content-length', r'%d' % len(data))
341 h.putheader(r'Content-length', r'%d' % len(data))
342 else:
342 else:
343 h.putrequest(
343 h.putrequest(
344 req.get_method(), urllibcompat.getselector(req),
344 req.get_method(), urllibcompat.getselector(req),
345 **skipheaders)
345 **skipheaders)
346 except socket.error as err:
346 except socket.error as err:
347 raise urlerr.urlerror(err)
347 raise urlerr.urlerror(err)
348 for k, v in headers.items():
348 for k, v in headers.items():
349 h.putheader(k, v)
349 h.putheader(k, v)
350 h.endheaders()
350 h.endheaders()
351 if urllibcompat.hasdata(req):
351 if urllibcompat.hasdata(req):
352 h.send(data)
352 h.send(data)
353
353
354 # This will fail to record events in case of I/O failure. That's OK.
354 # This will fail to record events in case of I/O failure. That's OK.
355 self.requestscount += 1
355 self.requestscount += 1
356 self.sentbytescount += getattr(h, 'sentbytescount', 0) - oldbytescount
356 self.sentbytescount += getattr(h, 'sentbytescount', 0) - oldbytescount
357
357
358 try:
358 try:
359 self.parent.requestscount += 1
359 self.parent.requestscount += 1
360 self.parent.sentbytescount += (
360 self.parent.sentbytescount += (
361 getattr(h, 'sentbytescount', 0) - oldbytescount)
361 getattr(h, 'sentbytescount', 0) - oldbytescount)
362 except AttributeError:
362 except AttributeError:
363 pass
363 pass
364
364
365 class HTTPHandler(KeepAliveHandler, urlreq.httphandler):
365 class HTTPHandler(KeepAliveHandler, urlreq.httphandler):
366 pass
366 pass
367
367
368 class HTTPResponse(httplib.HTTPResponse):
368 class HTTPResponse(httplib.HTTPResponse):
369 # we need to subclass HTTPResponse in order to
369 # we need to subclass HTTPResponse in order to
370 # 1) add readline(), readlines(), and readinto() methods
370 # 1) add readline(), readlines(), and readinto() methods
371 # 2) add close_connection() methods
371 # 2) add close_connection() methods
372 # 3) add info() and geturl() methods
372 # 3) add info() and geturl() methods
373
373
374 # in order to add readline(), read must be modified to deal with a
374 # in order to add readline(), read must be modified to deal with a
375 # buffer. example: readline must read a buffer and then spit back
375 # buffer. example: readline must read a buffer and then spit back
376 # one line at a time. The only real alternative is to read one
376 # one line at a time. The only real alternative is to read one
377 # BYTE at a time (ick). Once something has been read, it can't be
377 # BYTE at a time (ick). Once something has been read, it can't be
378 # put back (ok, maybe it can, but that's even uglier than this),
378 # put back (ok, maybe it can, but that's even uglier than this),
379 # so if you THEN do a normal read, you must first take stuff from
379 # so if you THEN do a normal read, you must first take stuff from
380 # the buffer.
380 # the buffer.
381
381
382 # the read method wraps the original to accommodate buffering,
382 # the read method wraps the original to accommodate buffering,
383 # although read() never adds to the buffer.
383 # although read() never adds to the buffer.
384 # Both readline and readlines have been stolen with almost no
384 # Both readline and readlines have been stolen with almost no
385 # modification from socket.py
385 # modification from socket.py
386
386
387
387
388 def __init__(self, sock, debuglevel=0, strict=0, method=None):
388 def __init__(self, sock, debuglevel=0, strict=0, method=None):
389 extrakw = {}
389 extrakw = {}
390 if not pycompat.ispy3:
390 if not pycompat.ispy3:
391 extrakw[r'strict'] = True
391 extrakw[r'strict'] = True
392 extrakw[r'buffering'] = True
392 extrakw[r'buffering'] = True
393 httplib.HTTPResponse.__init__(self, sock, debuglevel=debuglevel,
393 httplib.HTTPResponse.__init__(self, sock, debuglevel=debuglevel,
394 method=method, **extrakw)
394 method=method, **extrakw)
395 self.fileno = sock.fileno
395 self.fileno = sock.fileno
396 self.code = None
396 self.code = None
397 self.receivedbytescount = 0
397 self.receivedbytescount = 0
398 self._rbuf = ''
398 self._rbuf = ''
399 self._rbufsize = 8096
399 self._rbufsize = 8096
400 self._handler = None # inserted by the handler later
400 self._handler = None # inserted by the handler later
401 self._host = None # (same)
401 self._host = None # (same)
402 self._url = None # (same)
402 self._url = None # (same)
403 self._connection = None # (same)
403 self._connection = None # (same)
404
404
405 _raw_read = httplib.HTTPResponse.read
405 _raw_read = httplib.HTTPResponse.read
406 _raw_readinto = getattr(httplib.HTTPResponse, 'readinto', None)
406 _raw_readinto = getattr(httplib.HTTPResponse, 'readinto', None)
407
407
408 def close(self):
408 def close(self):
409 if self.fp:
409 if self.fp:
410 self.fp.close()
410 self.fp.close()
411 self.fp = None
411 self.fp = None
412 if self._handler:
412 if self._handler:
413 self._handler._request_closed(self, self._host,
413 self._handler._request_closed(self, self._host,
414 self._connection)
414 self._connection)
415
415
416 def close_connection(self):
416 def close_connection(self):
417 self._handler._remove_connection(self._host, self._connection, close=1)
417 self._handler._remove_connection(self._host, self._connection, close=1)
418 self.close()
418 self.close()
419
419
420 def info(self):
420 def info(self):
421 return self.headers
421 return self.headers
422
422
423 def geturl(self):
423 def geturl(self):
424 return self._url
424 return self._url
425
425
426 def read(self, amt=None):
426 def read(self, amt=None):
427 # the _rbuf test is only in this first if for speed. It's not
427 # the _rbuf test is only in this first if for speed. It's not
428 # logically necessary
428 # logically necessary
429 if self._rbuf and amt is not None:
429 if self._rbuf and amt is not None:
430 L = len(self._rbuf)
430 L = len(self._rbuf)
431 if amt > L:
431 if amt > L:
432 amt -= L
432 amt -= L
433 else:
433 else:
434 s = self._rbuf[:amt]
434 s = self._rbuf[:amt]
435 self._rbuf = self._rbuf[amt:]
435 self._rbuf = self._rbuf[amt:]
436 return s
436 return s
437 # Careful! http.client.HTTPResponse.read() on Python 3 is
437 # Careful! http.client.HTTPResponse.read() on Python 3 is
438 # implemented using readinto(), which can duplicate self._rbuf
438 # implemented using readinto(), which can duplicate self._rbuf
439 # if it's not empty.
439 # if it's not empty.
440 s = self._rbuf
440 s = self._rbuf
441 self._rbuf = ''
441 self._rbuf = ''
442 data = self._raw_read(amt)
442 data = self._raw_read(amt)
443
443
444 self.receivedbytescount += len(data)
444 self.receivedbytescount += len(data)
445 try:
445 try:
446 self._connection.receivedbytescount += len(data)
446 self._connection.receivedbytescount += len(data)
447 except AttributeError:
447 except AttributeError:
448 pass
448 pass
449 try:
449 try:
450 self._handler.parent.receivedbytescount += len(data)
450 self._handler.parent.receivedbytescount += len(data)
451 except AttributeError:
451 except AttributeError:
452 pass
452 pass
453
453
454 s += data
454 s += data
455 return s
455 return s
456
456
457 # stolen from Python SVN #68532 to fix issue1088
457 # stolen from Python SVN #68532 to fix issue1088
458 def _read_chunked(self, amt):
458 def _read_chunked(self, amt):
459 chunk_left = self.chunk_left
459 chunk_left = self.chunk_left
460 parts = []
460 parts = []
461
461
462 while True:
462 while True:
463 if chunk_left is None:
463 if chunk_left is None:
464 line = self.fp.readline()
464 line = self.fp.readline()
465 i = line.find(';')
465 i = line.find(';')
466 if i >= 0:
466 if i >= 0:
467 line = line[:i] # strip chunk-extensions
467 line = line[:i] # strip chunk-extensions
468 try:
468 try:
469 chunk_left = int(line, 16)
469 chunk_left = int(line, 16)
470 except ValueError:
470 except ValueError:
471 # close the connection as protocol synchronization is
471 # close the connection as protocol synchronization is
472 # probably lost
472 # probably lost
473 self.close()
473 self.close()
474 raise httplib.IncompleteRead(''.join(parts))
474 raise httplib.IncompleteRead(''.join(parts))
475 if chunk_left == 0:
475 if chunk_left == 0:
476 break
476 break
477 if amt is None:
477 if amt is None:
478 parts.append(self._safe_read(chunk_left))
478 parts.append(self._safe_read(chunk_left))
479 elif amt < chunk_left:
479 elif amt < chunk_left:
480 parts.append(self._safe_read(amt))
480 parts.append(self._safe_read(amt))
481 self.chunk_left = chunk_left - amt
481 self.chunk_left = chunk_left - amt
482 return ''.join(parts)
482 return ''.join(parts)
483 elif amt == chunk_left:
483 elif amt == chunk_left:
484 parts.append(self._safe_read(amt))
484 parts.append(self._safe_read(amt))
485 self._safe_read(2) # toss the CRLF at the end of the chunk
485 self._safe_read(2) # toss the CRLF at the end of the chunk
486 self.chunk_left = None
486 self.chunk_left = None
487 return ''.join(parts)
487 return ''.join(parts)
488 else:
488 else:
489 parts.append(self._safe_read(chunk_left))
489 parts.append(self._safe_read(chunk_left))
490 amt -= chunk_left
490 amt -= chunk_left
491
491
492 # we read the whole chunk, get another
492 # we read the whole chunk, get another
493 self._safe_read(2) # toss the CRLF at the end of the chunk
493 self._safe_read(2) # toss the CRLF at the end of the chunk
494 chunk_left = None
494 chunk_left = None
495
495
496 # read and discard trailer up to the CRLF terminator
496 # read and discard trailer up to the CRLF terminator
497 ### note: we shouldn't have any trailers!
497 ### note: we shouldn't have any trailers!
498 while True:
498 while True:
499 line = self.fp.readline()
499 line = self.fp.readline()
500 if not line:
500 if not line:
501 # a vanishingly small number of sites EOF without
501 # a vanishingly small number of sites EOF without
502 # sending the trailer
502 # sending the trailer
503 break
503 break
504 if line == '\r\n':
504 if line == '\r\n':
505 break
505 break
506
506
507 # we read everything; close the "file"
507 # we read everything; close the "file"
508 self.close()
508 self.close()
509
509
510 return ''.join(parts)
510 return ''.join(parts)
511
511
512 def readline(self):
512 def readline(self):
513 # Fast path for a line is already available in read buffer.
513 # Fast path for a line is already available in read buffer.
514 i = self._rbuf.find('\n')
514 i = self._rbuf.find('\n')
515 if i >= 0:
515 if i >= 0:
516 i += 1
516 i += 1
517 line = self._rbuf[:i]
517 line = self._rbuf[:i]
518 self._rbuf = self._rbuf[i:]
518 self._rbuf = self._rbuf[i:]
519 return line
519 return line
520
520
521 # No newline in local buffer. Read until we find one.
521 # No newline in local buffer. Read until we find one.
522 chunks = [self._rbuf]
522 chunks = [self._rbuf]
523 i = -1
523 i = -1
524 readsize = self._rbufsize
524 readsize = self._rbufsize
525 while True:
525 while True:
526 new = self._raw_read(readsize)
526 new = self._raw_read(readsize)
527 if not new:
527 if not new:
528 break
528 break
529
529
530 self.receivedbytescount += len(new)
530 self.receivedbytescount += len(new)
531 self._connection.receivedbytescount += len(new)
531 self._connection.receivedbytescount += len(new)
532 try:
532 try:
533 self._handler.parent.receivedbytescount += len(new)
533 self._handler.parent.receivedbytescount += len(new)
534 except AttributeError:
534 except AttributeError:
535 pass
535 pass
536
536
537 chunks.append(new)
537 chunks.append(new)
538 i = new.find('\n')
538 i = new.find('\n')
539 if i >= 0:
539 if i >= 0:
540 break
540 break
541
541
542 # We either have exhausted the stream or have a newline in chunks[-1].
542 # We either have exhausted the stream or have a newline in chunks[-1].
543
543
544 # EOF
544 # EOF
545 if i == -1:
545 if i == -1:
546 self._rbuf = ''
546 self._rbuf = ''
547 return ''.join(chunks)
547 return ''.join(chunks)
548
548
549 i += 1
549 i += 1
550 self._rbuf = chunks[-1][i:]
550 self._rbuf = chunks[-1][i:]
551 chunks[-1] = chunks[-1][:i]
551 chunks[-1] = chunks[-1][:i]
552 return ''.join(chunks)
552 return ''.join(chunks)
553
553
554 def readlines(self, sizehint=0):
554 def readlines(self, sizehint=0):
555 total = 0
555 total = 0
556 list = []
556 list = []
557 while True:
557 while True:
558 line = self.readline()
558 line = self.readline()
559 if not line:
559 if not line:
560 break
560 break
561 list.append(line)
561 list.append(line)
562 total += len(line)
562 total += len(line)
563 if sizehint and total >= sizehint:
563 if sizehint and total >= sizehint:
564 break
564 break
565 return list
565 return list
566
566
567 def readinto(self, dest):
567 def readinto(self, dest):
568 if self._raw_readinto is None:
568 if self._raw_readinto is None:
569 res = self.read(len(dest))
569 res = self.read(len(dest))
570 if not res:
570 if not res:
571 return 0
571 return 0
572 dest[0:len(res)] = res
572 dest[0:len(res)] = res
573 return len(res)
573 return len(res)
574 total = len(dest)
574 total = len(dest)
575 have = len(self._rbuf)
575 have = len(self._rbuf)
576 if have >= total:
576 if have >= total:
577 dest[0:total] = self._rbuf[:total]
577 dest[0:total] = self._rbuf[:total]
578 self._rbuf = self._rbuf[total:]
578 self._rbuf = self._rbuf[total:]
579 return total
579 return total
580 mv = memoryview(dest)
580 mv = memoryview(dest)
581 got = self._raw_readinto(mv[have:total])
581 got = self._raw_readinto(mv[have:total])
582
582
583 self.receivedbytescount += got
583 self.receivedbytescount += got
584 self._connection.receivedbytescount += got
584 self._connection.receivedbytescount += got
585 try:
585 try:
586 self._handler.receivedbytescount += got
586 self._handler.receivedbytescount += got
587 except AttributeError:
587 except AttributeError:
588 pass
588 pass
589
589
590 dest[0:have] = self._rbuf
590 dest[0:have] = self._rbuf
591 got += len(self._rbuf)
591 got += len(self._rbuf)
592 self._rbuf = ''
592 self._rbuf = ''
593 return got
593 return got
594
594
595 def safesend(self, str):
595 def safesend(self, str):
596 """Send `str' to the server.
596 """Send `str' to the server.
597
597
598 Shamelessly ripped off from httplib to patch a bad behavior.
598 Shamelessly ripped off from httplib to patch a bad behavior.
599 """
599 """
600 # _broken_pipe_resp is an attribute we set in this function
600 # _broken_pipe_resp is an attribute we set in this function
601 # if the socket is closed while we're sending data but
601 # if the socket is closed while we're sending data but
602 # the server sent us a response before hanging up.
602 # the server sent us a response before hanging up.
603 # In that case, we want to pretend to send the rest of the
603 # In that case, we want to pretend to send the rest of the
604 # outgoing data, and then let the user use getresponse()
604 # outgoing data, and then let the user use getresponse()
605 # (which we wrap) to get this last response before
605 # (which we wrap) to get this last response before
606 # opening a new socket.
606 # opening a new socket.
607 if getattr(self, '_broken_pipe_resp', None) is not None:
607 if getattr(self, '_broken_pipe_resp', None) is not None:
608 return
608 return
609
609
610 if self.sock is None:
610 if self.sock is None:
611 if self.auto_open:
611 if self.auto_open:
612 self.connect()
612 self.connect()
613 else:
613 else:
614 raise httplib.NotConnected
614 raise httplib.NotConnected
615
615
616 # send the data to the server. if we get a broken pipe, then close
616 # send the data to the server. if we get a broken pipe, then close
617 # the socket. we want to reconnect when somebody tries to send again.
617 # the socket. we want to reconnect when somebody tries to send again.
618 #
618 #
619 # NOTE: we DO propagate the error, though, because we cannot simply
619 # NOTE: we DO propagate the error, though, because we cannot simply
620 # ignore the error... the caller will know if they can retry.
620 # ignore the error... the caller will know if they can retry.
621 if self.debuglevel > 0:
621 if self.debuglevel > 0:
622 print("send:", repr(str))
622 print("send:", repr(str))
623 try:
623 try:
624 blocksize = 8192
624 blocksize = 8192
625 read = getattr(str, 'read', None)
625 read = getattr(str, 'read', None)
626 if read is not None:
626 if read is not None:
627 if self.debuglevel > 0:
627 if self.debuglevel > 0:
628 print("sending a read()able")
628 print("sending a read()able")
629 data = read(blocksize)
629 data = read(blocksize)
630 while data:
630 while data:
631 self.sock.sendall(data)
631 self.sock.sendall(data)
632 self.sentbytescount += len(data)
632 self.sentbytescount += len(data)
633 data = read(blocksize)
633 data = read(blocksize)
634 else:
634 else:
635 self.sock.sendall(str)
635 self.sock.sendall(str)
636 self.sentbytescount += len(str)
636 self.sentbytescount += len(str)
637 except socket.error as v:
637 except socket.error as v:
638 reraise = True
638 reraise = True
639 if v[0] == errno.EPIPE: # Broken pipe
639 if v.args[0] == errno.EPIPE: # Broken pipe
640 if self._HTTPConnection__state == httplib._CS_REQ_SENT:
640 if self._HTTPConnection__state == httplib._CS_REQ_SENT:
641 self._broken_pipe_resp = None
641 self._broken_pipe_resp = None
642 self._broken_pipe_resp = self.getresponse()
642 self._broken_pipe_resp = self.getresponse()
643 reraise = False
643 reraise = False
644 self.close()
644 self.close()
645 if reraise:
645 if reraise:
646 raise
646 raise
647
647
648 def wrapgetresponse(cls):
648 def wrapgetresponse(cls):
649 """Wraps getresponse in cls with a broken-pipe sane version.
649 """Wraps getresponse in cls with a broken-pipe sane version.
650 """
650 """
651 def safegetresponse(self):
651 def safegetresponse(self):
652 # In safesend() we might set the _broken_pipe_resp
652 # In safesend() we might set the _broken_pipe_resp
653 # attribute, in which case the socket has already
653 # attribute, in which case the socket has already
654 # been closed and we just need to give them the response
654 # been closed and we just need to give them the response
655 # back. Otherwise, we use the normal response path.
655 # back. Otherwise, we use the normal response path.
656 r = getattr(self, '_broken_pipe_resp', None)
656 r = getattr(self, '_broken_pipe_resp', None)
657 if r is not None:
657 if r is not None:
658 return r
658 return r
659 return cls.getresponse(self)
659 return cls.getresponse(self)
660 safegetresponse.__doc__ = cls.getresponse.__doc__
660 safegetresponse.__doc__ = cls.getresponse.__doc__
661 return safegetresponse
661 return safegetresponse
662
662
663 class HTTPConnection(httplib.HTTPConnection):
663 class HTTPConnection(httplib.HTTPConnection):
664 # url.httpsconnection inherits from this. So when adding/removing
664 # url.httpsconnection inherits from this. So when adding/removing
665 # attributes, be sure to audit httpsconnection() for unintended
665 # attributes, be sure to audit httpsconnection() for unintended
666 # consequences.
666 # consequences.
667
667
668 # use the modified response class
668 # use the modified response class
669 response_class = HTTPResponse
669 response_class = HTTPResponse
670 send = safesend
670 send = safesend
671 getresponse = wrapgetresponse(httplib.HTTPConnection)
671 getresponse = wrapgetresponse(httplib.HTTPConnection)
672
672
673 def __init__(self, *args, **kwargs):
673 def __init__(self, *args, **kwargs):
674 httplib.HTTPConnection.__init__(self, *args, **kwargs)
674 httplib.HTTPConnection.__init__(self, *args, **kwargs)
675 self.sentbytescount = 0
675 self.sentbytescount = 0
676 self.receivedbytescount = 0
676 self.receivedbytescount = 0
677
677
678 #########################################################################
678 #########################################################################
679 ##### TEST FUNCTIONS
679 ##### TEST FUNCTIONS
680 #########################################################################
680 #########################################################################
681
681
682
682
683 def continuity(url):
683 def continuity(url):
684 md5 = hashlib.md5
684 md5 = hashlib.md5
685 format = '%25s: %s'
685 format = '%25s: %s'
686
686
687 # first fetch the file with the normal http handler
687 # first fetch the file with the normal http handler
688 opener = urlreq.buildopener()
688 opener = urlreq.buildopener()
689 urlreq.installopener(opener)
689 urlreq.installopener(opener)
690 fo = urlreq.urlopen(url)
690 fo = urlreq.urlopen(url)
691 foo = fo.read()
691 foo = fo.read()
692 fo.close()
692 fo.close()
693 m = md5(foo)
693 m = md5(foo)
694 print(format % ('normal urllib', node.hex(m.digest())))
694 print(format % ('normal urllib', node.hex(m.digest())))
695
695
696 # now install the keepalive handler and try again
696 # now install the keepalive handler and try again
697 opener = urlreq.buildopener(HTTPHandler())
697 opener = urlreq.buildopener(HTTPHandler())
698 urlreq.installopener(opener)
698 urlreq.installopener(opener)
699
699
700 fo = urlreq.urlopen(url)
700 fo = urlreq.urlopen(url)
701 foo = fo.read()
701 foo = fo.read()
702 fo.close()
702 fo.close()
703 m = md5(foo)
703 m = md5(foo)
704 print(format % ('keepalive read', node.hex(m.digest())))
704 print(format % ('keepalive read', node.hex(m.digest())))
705
705
706 fo = urlreq.urlopen(url)
706 fo = urlreq.urlopen(url)
707 foo = ''
707 foo = ''
708 while True:
708 while True:
709 f = fo.readline()
709 f = fo.readline()
710 if f:
710 if f:
711 foo = foo + f
711 foo = foo + f
712 else:
712 else:
713 break
713 break
714 fo.close()
714 fo.close()
715 m = md5(foo)
715 m = md5(foo)
716 print(format % ('keepalive readline', node.hex(m.digest())))
716 print(format % ('keepalive readline', node.hex(m.digest())))
717
717
718 def comp(N, url):
718 def comp(N, url):
719 print(' making %i connections to:\n %s' % (N, url))
719 print(' making %i connections to:\n %s' % (N, url))
720
720
721 procutil.stdout.write(' first using the normal urllib handlers')
721 procutil.stdout.write(' first using the normal urllib handlers')
722 # first use normal opener
722 # first use normal opener
723 opener = urlreq.buildopener()
723 opener = urlreq.buildopener()
724 urlreq.installopener(opener)
724 urlreq.installopener(opener)
725 t1 = fetch(N, url)
725 t1 = fetch(N, url)
726 print(' TIME: %.3f s' % t1)
726 print(' TIME: %.3f s' % t1)
727
727
728 procutil.stdout.write(' now using the keepalive handler ')
728 procutil.stdout.write(' now using the keepalive handler ')
729 # now install the keepalive handler and try again
729 # now install the keepalive handler and try again
730 opener = urlreq.buildopener(HTTPHandler())
730 opener = urlreq.buildopener(HTTPHandler())
731 urlreq.installopener(opener)
731 urlreq.installopener(opener)
732 t2 = fetch(N, url)
732 t2 = fetch(N, url)
733 print(' TIME: %.3f s' % t2)
733 print(' TIME: %.3f s' % t2)
734 print(' improvement factor: %.2f' % (t1 / t2))
734 print(' improvement factor: %.2f' % (t1 / t2))
735
735
736 def fetch(N, url, delay=0):
736 def fetch(N, url, delay=0):
737 import time
737 import time
738 lens = []
738 lens = []
739 starttime = time.time()
739 starttime = time.time()
740 for i in range(N):
740 for i in range(N):
741 if delay and i > 0:
741 if delay and i > 0:
742 time.sleep(delay)
742 time.sleep(delay)
743 fo = urlreq.urlopen(url)
743 fo = urlreq.urlopen(url)
744 foo = fo.read()
744 foo = fo.read()
745 fo.close()
745 fo.close()
746 lens.append(len(foo))
746 lens.append(len(foo))
747 diff = time.time() - starttime
747 diff = time.time() - starttime
748
748
749 j = 0
749 j = 0
750 for i in lens[1:]:
750 for i in lens[1:]:
751 j = j + 1
751 j = j + 1
752 if not i == lens[0]:
752 if not i == lens[0]:
753 print("WARNING: inconsistent length on read %i: %i" % (j, i))
753 print("WARNING: inconsistent length on read %i: %i" % (j, i))
754
754
755 return diff
755 return diff
756
756
757 def test_timeout(url):
757 def test_timeout(url):
758 global DEBUG
758 global DEBUG
759 dbbackup = DEBUG
759 dbbackup = DEBUG
760 class FakeLogger(object):
760 class FakeLogger(object):
761 def debug(self, msg, *args):
761 def debug(self, msg, *args):
762 print(msg % args)
762 print(msg % args)
763 info = warning = error = debug
763 info = warning = error = debug
764 DEBUG = FakeLogger()
764 DEBUG = FakeLogger()
765 print(" fetching the file to establish a connection")
765 print(" fetching the file to establish a connection")
766 fo = urlreq.urlopen(url)
766 fo = urlreq.urlopen(url)
767 data1 = fo.read()
767 data1 = fo.read()
768 fo.close()
768 fo.close()
769
769
770 i = 20
770 i = 20
771 print(" waiting %i seconds for the server to close the connection" % i)
771 print(" waiting %i seconds for the server to close the connection" % i)
772 while i > 0:
772 while i > 0:
773 procutil.stdout.write('\r %2i' % i)
773 procutil.stdout.write('\r %2i' % i)
774 procutil.stdout.flush()
774 procutil.stdout.flush()
775 time.sleep(1)
775 time.sleep(1)
776 i -= 1
776 i -= 1
777 procutil.stderr.write('\r')
777 procutil.stderr.write('\r')
778
778
779 print(" fetching the file a second time")
779 print(" fetching the file a second time")
780 fo = urlreq.urlopen(url)
780 fo = urlreq.urlopen(url)
781 data2 = fo.read()
781 data2 = fo.read()
782 fo.close()
782 fo.close()
783
783
784 if data1 == data2:
784 if data1 == data2:
785 print(' data are identical')
785 print(' data are identical')
786 else:
786 else:
787 print(' ERROR: DATA DIFFER')
787 print(' ERROR: DATA DIFFER')
788
788
789 DEBUG = dbbackup
789 DEBUG = dbbackup
790
790
791
791
792 def test(url, N=10):
792 def test(url, N=10):
793 print("performing continuity test (making sure stuff isn't corrupted)")
793 print("performing continuity test (making sure stuff isn't corrupted)")
794 continuity(url)
794 continuity(url)
795 print('')
795 print('')
796 print("performing speed comparison")
796 print("performing speed comparison")
797 comp(N, url)
797 comp(N, url)
798 print('')
798 print('')
799 print("performing dropped-connection check")
799 print("performing dropped-connection check")
800 test_timeout(url)
800 test_timeout(url)
801
801
802 if __name__ == '__main__':
802 if __name__ == '__main__':
803 import time
803 import time
804 try:
804 try:
805 N = int(sys.argv[1])
805 N = int(sys.argv[1])
806 url = sys.argv[2]
806 url = sys.argv[2]
807 except (IndexError, ValueError):
807 except (IndexError, ValueError):
808 print("%s <integer> <url>" % sys.argv[0])
808 print("%s <integer> <url>" % sys.argv[0])
809 else:
809 else:
810 test(url, N)
810 test(url, N)
General Comments 0
You need to be logged in to leave comments. Login now