##// END OF EJS Templates
hgweb: remove Python 2 support code...
Gregory Szorc -
r49761:e453c698 default
parent child Browse files
Show More
@@ -1,443 +1,436 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 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@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
9
10 import errno
10 import errno
11 import importlib
11 import importlib
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 from ..pycompat import (
19 from ..pycompat import (
20 getattr,
20 getattr,
21 open,
21 open,
22 )
22 )
23
23
24 from .. import (
24 from .. import (
25 encoding,
25 encoding,
26 error,
26 error,
27 pycompat,
27 pycompat,
28 util,
28 util,
29 )
29 )
30 from ..utils import (
30 from ..utils import (
31 urlutil,
31 urlutil,
32 )
32 )
33
33
34 httpservermod = util.httpserver
34 httpservermod = util.httpserver
35 socketserver = util.socketserver
35 socketserver = util.socketserver
36 urlerr = util.urlerr
36 urlerr = util.urlerr
37 urlreq = util.urlreq
37 urlreq = util.urlreq
38
38
39 from . import common
39 from . import common
40
40
41
41
42 def _splitURI(uri):
42 def _splitURI(uri):
43 """Return path and query that has been split from uri
43 """Return path and query that has been split from uri
44
44
45 Just like CGI environment, the path is unquoted, the query is
45 Just like CGI environment, the path is unquoted, the query is
46 not.
46 not.
47 """
47 """
48 if '?' in uri:
48 if '?' in uri:
49 path, query = uri.split('?', 1)
49 path, query = uri.split('?', 1)
50 else:
50 else:
51 path, query = uri, r''
51 path, query = uri, r''
52 return urlreq.unquote(path), query
52 return urlreq.unquote(path), query
53
53
54
54
55 class _error_logger(object):
55 class _error_logger(object):
56 def __init__(self, handler):
56 def __init__(self, handler):
57 self.handler = handler
57 self.handler = handler
58
58
59 def flush(self):
59 def flush(self):
60 pass
60 pass
61
61
62 def write(self, str):
62 def write(self, str):
63 self.writelines(str.split(b'\n'))
63 self.writelines(str.split(b'\n'))
64
64
65 def writelines(self, seq):
65 def writelines(self, seq):
66 for msg in seq:
66 for msg in seq:
67 self.handler.log_error("HG error: %s", encoding.strfromlocal(msg))
67 self.handler.log_error("HG error: %s", encoding.strfromlocal(msg))
68
68
69
69
70 class _httprequesthandler(httpservermod.basehttprequesthandler):
70 class _httprequesthandler(httpservermod.basehttprequesthandler):
71
71
72 url_scheme = b'http'
72 url_scheme = b'http'
73
73
74 @staticmethod
74 @staticmethod
75 def preparehttpserver(httpserver, ui):
75 def preparehttpserver(httpserver, ui):
76 """Prepare .socket of new HTTPServer instance"""
76 """Prepare .socket of new HTTPServer instance"""
77
77
78 def __init__(self, *args, **kargs):
78 def __init__(self, *args, **kargs):
79 self.protocol_version = r'HTTP/1.1'
79 self.protocol_version = r'HTTP/1.1'
80 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
80 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
81
81
82 def _log_any(self, fp, format, *args):
82 def _log_any(self, fp, format, *args):
83 fp.write(
83 fp.write(
84 pycompat.sysbytes(
84 pycompat.sysbytes(
85 r"%s - - [%s] %s"
85 r"%s - - [%s] %s"
86 % (
86 % (
87 self.client_address[0],
87 self.client_address[0],
88 self.log_date_time_string(),
88 self.log_date_time_string(),
89 format % args,
89 format % args,
90 )
90 )
91 )
91 )
92 + b'\n'
92 + b'\n'
93 )
93 )
94 fp.flush()
94 fp.flush()
95
95
96 def log_error(self, format, *args):
96 def log_error(self, format, *args):
97 self._log_any(self.server.errorlog, format, *args)
97 self._log_any(self.server.errorlog, format, *args)
98
98
99 def log_message(self, format, *args):
99 def log_message(self, format, *args):
100 self._log_any(self.server.accesslog, format, *args)
100 self._log_any(self.server.accesslog, format, *args)
101
101
102 def log_request(self, code='-', size='-'):
102 def log_request(self, code='-', size='-'):
103 xheaders = []
103 xheaders = []
104 if util.safehasattr(self, b'headers'):
104 if util.safehasattr(self, b'headers'):
105 xheaders = [
105 xheaders = [
106 h for h in self.headers.items() if h[0].startswith('x-')
106 h for h in self.headers.items() if h[0].startswith('x-')
107 ]
107 ]
108 self.log_message(
108 self.log_message(
109 '"%s" %s %s%s',
109 '"%s" %s %s%s',
110 self.requestline,
110 self.requestline,
111 str(code),
111 str(code),
112 str(size),
112 str(size),
113 ''.join([' %s:%s' % h for h in sorted(xheaders)]),
113 ''.join([' %s:%s' % h for h in sorted(xheaders)]),
114 )
114 )
115
115
116 def do_write(self):
116 def do_write(self):
117 try:
117 try:
118 self.do_hgweb()
118 self.do_hgweb()
119 except socket.error as inst:
119 except socket.error as inst:
120 if inst.errno != errno.EPIPE:
120 if inst.errno != errno.EPIPE:
121 raise
121 raise
122
122
123 def do_POST(self):
123 def do_POST(self):
124 try:
124 try:
125 self.do_write()
125 self.do_write()
126 except Exception as e:
126 except Exception as e:
127 # I/O below could raise another exception. So log the original
127 # I/O below could raise another exception. So log the original
128 # exception first to ensure it is recorded.
128 # exception first to ensure it is recorded.
129 if not (
129 if not (
130 isinstance(e, (OSError, socket.error))
130 isinstance(e, (OSError, socket.error))
131 and e.errno == errno.ECONNRESET
131 and e.errno == errno.ECONNRESET
132 ):
132 ):
133 tb = "".join(traceback.format_exception(*sys.exc_info()))
133 tb = "".join(traceback.format_exception(*sys.exc_info()))
134 # We need a native-string newline to poke in the log
134 # We need a native-string newline to poke in the log
135 # message, because we won't get a newline when using an
135 # message, because we won't get a newline when using an
136 # r-string. This is the easy way out.
136 # r-string. This is the easy way out.
137 newline = chr(10)
137 newline = chr(10)
138 self.log_error(
138 self.log_error(
139 r"Exception happened during processing "
139 r"Exception happened during processing "
140 "request '%s':%s%s",
140 "request '%s':%s%s",
141 self.path,
141 self.path,
142 newline,
142 newline,
143 tb,
143 tb,
144 )
144 )
145
145
146 self._start_response("500 Internal Server Error", [])
146 self._start_response("500 Internal Server Error", [])
147 self._write(b"Internal Server Error")
147 self._write(b"Internal Server Error")
148 self._done()
148 self._done()
149
149
150 def do_PUT(self):
150 def do_PUT(self):
151 self.do_POST()
151 self.do_POST()
152
152
153 def do_GET(self):
153 def do_GET(self):
154 self.do_POST()
154 self.do_POST()
155
155
156 def do_hgweb(self):
156 def do_hgweb(self):
157 self.sent_headers = False
157 self.sent_headers = False
158 path, query = _splitURI(self.path)
158 path, query = _splitURI(self.path)
159
159
160 # Ensure the slicing of path below is valid
160 # Ensure the slicing of path below is valid
161 if path != self.server.prefix and not path.startswith(
161 if path != self.server.prefix and not path.startswith(
162 self.server.prefix + b'/'
162 self.server.prefix + b'/'
163 ):
163 ):
164 self._start_response(pycompat.strurl(common.statusmessage(404)), [])
164 self._start_response(pycompat.strurl(common.statusmessage(404)), [])
165 if self.command == 'POST':
165 if self.command == 'POST':
166 # Paranoia: tell the client we're going to close the
166 # Paranoia: tell the client we're going to close the
167 # socket so they don't try and reuse a socket that
167 # socket so they don't try and reuse a socket that
168 # might have a POST body waiting to confuse us. We do
168 # might have a POST body waiting to confuse us. We do
169 # this by directly munging self.saved_headers because
169 # this by directly munging self.saved_headers because
170 # self._start_response ignores Connection headers.
170 # self._start_response ignores Connection headers.
171 self.saved_headers = [('Connection', 'Close')]
171 self.saved_headers = [('Connection', 'Close')]
172 self._write(b"Not Found")
172 self._write(b"Not Found")
173 self._done()
173 self._done()
174 return
174 return
175
175
176 env = {}
176 env = {}
177 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
177 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
178 env['REQUEST_METHOD'] = self.command
178 env['REQUEST_METHOD'] = self.command
179 env['SERVER_NAME'] = self.server.server_name
179 env['SERVER_NAME'] = self.server.server_name
180 env['SERVER_PORT'] = str(self.server.server_port)
180 env['SERVER_PORT'] = str(self.server.server_port)
181 env['REQUEST_URI'] = self.path
181 env['REQUEST_URI'] = self.path
182 env['SCRIPT_NAME'] = pycompat.sysstr(self.server.prefix)
182 env['SCRIPT_NAME'] = pycompat.sysstr(self.server.prefix)
183 env['PATH_INFO'] = pycompat.sysstr(path[len(self.server.prefix) :])
183 env['PATH_INFO'] = pycompat.sysstr(path[len(self.server.prefix) :])
184 env['REMOTE_HOST'] = self.client_address[0]
184 env['REMOTE_HOST'] = self.client_address[0]
185 env['REMOTE_ADDR'] = self.client_address[0]
185 env['REMOTE_ADDR'] = self.client_address[0]
186 env['QUERY_STRING'] = query or ''
186 env['QUERY_STRING'] = query or ''
187
187
188 if pycompat.ispy3:
189 if self.headers.get_content_type() is None:
188 if self.headers.get_content_type() is None:
190 env['CONTENT_TYPE'] = self.headers.get_default_type()
189 env['CONTENT_TYPE'] = self.headers.get_default_type()
191 else:
190 else:
192 env['CONTENT_TYPE'] = self.headers.get_content_type()
191 env['CONTENT_TYPE'] = self.headers.get_content_type()
193 length = self.headers.get('content-length')
192 length = self.headers.get('content-length')
194 else:
195 if self.headers.typeheader is None:
196 env['CONTENT_TYPE'] = self.headers.type
197 else:
198 env['CONTENT_TYPE'] = self.headers.typeheader
199 length = self.headers.getheader('content-length')
200 if length:
193 if length:
201 env['CONTENT_LENGTH'] = length
194 env['CONTENT_LENGTH'] = length
202 for header in [
195 for header in [
203 h
196 h
204 for h in self.headers.keys()
197 for h in self.headers.keys()
205 if h.lower() not in ('content-type', 'content-length')
198 if h.lower() not in ('content-type', 'content-length')
206 ]:
199 ]:
207 hkey = 'HTTP_' + header.replace('-', '_').upper()
200 hkey = 'HTTP_' + header.replace('-', '_').upper()
208 hval = self.headers.get(header)
201 hval = self.headers.get(header)
209 hval = hval.replace('\n', '').strip()
202 hval = hval.replace('\n', '').strip()
210 if hval:
203 if hval:
211 env[hkey] = hval
204 env[hkey] = hval
212 env['SERVER_PROTOCOL'] = self.request_version
205 env['SERVER_PROTOCOL'] = self.request_version
213 env['wsgi.version'] = (1, 0)
206 env['wsgi.version'] = (1, 0)
214 env['wsgi.url_scheme'] = pycompat.sysstr(self.url_scheme)
207 env['wsgi.url_scheme'] = pycompat.sysstr(self.url_scheme)
215 if env.get('HTTP_EXPECT', b'').lower() == b'100-continue':
208 if env.get('HTTP_EXPECT', b'').lower() == b'100-continue':
216 self.rfile = common.continuereader(self.rfile, self.wfile.write)
209 self.rfile = common.continuereader(self.rfile, self.wfile.write)
217
210
218 env['wsgi.input'] = self.rfile
211 env['wsgi.input'] = self.rfile
219 env['wsgi.errors'] = _error_logger(self)
212 env['wsgi.errors'] = _error_logger(self)
220 env['wsgi.multithread'] = isinstance(
213 env['wsgi.multithread'] = isinstance(
221 self.server, socketserver.ThreadingMixIn
214 self.server, socketserver.ThreadingMixIn
222 )
215 )
223 if util.safehasattr(socketserver, b'ForkingMixIn'):
216 if util.safehasattr(socketserver, b'ForkingMixIn'):
224 env['wsgi.multiprocess'] = isinstance(
217 env['wsgi.multiprocess'] = isinstance(
225 self.server, socketserver.ForkingMixIn
218 self.server, socketserver.ForkingMixIn
226 )
219 )
227 else:
220 else:
228 env['wsgi.multiprocess'] = False
221 env['wsgi.multiprocess'] = False
229
222
230 env['wsgi.run_once'] = 0
223 env['wsgi.run_once'] = 0
231
224
232 wsgiref.validate.check_environ(env)
225 wsgiref.validate.check_environ(env)
233
226
234 self.saved_status = None
227 self.saved_status = None
235 self.saved_headers = []
228 self.saved_headers = []
236 self.length = None
229 self.length = None
237 self._chunked = None
230 self._chunked = None
238 for chunk in self.server.application(env, self._start_response):
231 for chunk in self.server.application(env, self._start_response):
239 self._write(chunk)
232 self._write(chunk)
240 if not self.sent_headers:
233 if not self.sent_headers:
241 self.send_headers()
234 self.send_headers()
242 self._done()
235 self._done()
243
236
244 def send_headers(self):
237 def send_headers(self):
245 if not self.saved_status:
238 if not self.saved_status:
246 raise AssertionError(
239 raise AssertionError(
247 b"Sending headers before start_response() called"
240 b"Sending headers before start_response() called"
248 )
241 )
249 saved_status = self.saved_status.split(None, 1)
242 saved_status = self.saved_status.split(None, 1)
250 saved_status[0] = int(saved_status[0])
243 saved_status[0] = int(saved_status[0])
251 self.send_response(*saved_status)
244 self.send_response(*saved_status)
252 self.length = None
245 self.length = None
253 self._chunked = False
246 self._chunked = False
254 for h in self.saved_headers:
247 for h in self.saved_headers:
255 self.send_header(*h)
248 self.send_header(*h)
256 if h[0].lower() == 'content-length':
249 if h[0].lower() == 'content-length':
257 self.length = int(h[1])
250 self.length = int(h[1])
258 if self.length is None and saved_status[0] != common.HTTP_NOT_MODIFIED:
251 if self.length is None and saved_status[0] != common.HTTP_NOT_MODIFIED:
259 self._chunked = (
252 self._chunked = (
260 not self.close_connection and self.request_version == 'HTTP/1.1'
253 not self.close_connection and self.request_version == 'HTTP/1.1'
261 )
254 )
262 if self._chunked:
255 if self._chunked:
263 self.send_header('Transfer-Encoding', 'chunked')
256 self.send_header('Transfer-Encoding', 'chunked')
264 else:
257 else:
265 self.send_header('Connection', 'close')
258 self.send_header('Connection', 'close')
266 self.end_headers()
259 self.end_headers()
267 self.sent_headers = True
260 self.sent_headers = True
268
261
269 def _start_response(self, http_status, headers, exc_info=None):
262 def _start_response(self, http_status, headers, exc_info=None):
270 assert isinstance(http_status, str)
263 assert isinstance(http_status, str)
271 code, msg = http_status.split(None, 1)
264 code, msg = http_status.split(None, 1)
272 code = int(code)
265 code = int(code)
273 self.saved_status = http_status
266 self.saved_status = http_status
274 bad_headers = ('connection', 'transfer-encoding')
267 bad_headers = ('connection', 'transfer-encoding')
275 self.saved_headers = [
268 self.saved_headers = [
276 h for h in headers if h[0].lower() not in bad_headers
269 h for h in headers if h[0].lower() not in bad_headers
277 ]
270 ]
278 return self._write
271 return self._write
279
272
280 def _write(self, data):
273 def _write(self, data):
281 if not self.saved_status:
274 if not self.saved_status:
282 raise AssertionError(b"data written before start_response() called")
275 raise AssertionError(b"data written before start_response() called")
283 elif not self.sent_headers:
276 elif not self.sent_headers:
284 self.send_headers()
277 self.send_headers()
285 if self.length is not None:
278 if self.length is not None:
286 if len(data) > self.length:
279 if len(data) > self.length:
287 raise AssertionError(
280 raise AssertionError(
288 b"Content-length header sent, but more "
281 b"Content-length header sent, but more "
289 b"bytes than specified are being written."
282 b"bytes than specified are being written."
290 )
283 )
291 self.length = self.length - len(data)
284 self.length = self.length - len(data)
292 elif self._chunked and data:
285 elif self._chunked and data:
293 data = b'%x\r\n%s\r\n' % (len(data), data)
286 data = b'%x\r\n%s\r\n' % (len(data), data)
294 self.wfile.write(data)
287 self.wfile.write(data)
295 self.wfile.flush()
288 self.wfile.flush()
296
289
297 def _done(self):
290 def _done(self):
298 if self._chunked:
291 if self._chunked:
299 self.wfile.write(b'0\r\n\r\n')
292 self.wfile.write(b'0\r\n\r\n')
300 self.wfile.flush()
293 self.wfile.flush()
301
294
302 def version_string(self):
295 def version_string(self):
303 if self.server.serverheader:
296 if self.server.serverheader:
304 return encoding.strfromlocal(self.server.serverheader)
297 return encoding.strfromlocal(self.server.serverheader)
305 return httpservermod.basehttprequesthandler.version_string(self)
298 return httpservermod.basehttprequesthandler.version_string(self)
306
299
307
300
308 class _httprequesthandlerssl(_httprequesthandler):
301 class _httprequesthandlerssl(_httprequesthandler):
309 """HTTPS handler based on Python's ssl module"""
302 """HTTPS handler based on Python's ssl module"""
310
303
311 url_scheme = b'https'
304 url_scheme = b'https'
312
305
313 @staticmethod
306 @staticmethod
314 def preparehttpserver(httpserver, ui):
307 def preparehttpserver(httpserver, ui):
315 try:
308 try:
316 from .. import sslutil
309 from .. import sslutil
317
310
318 sslutil.wrapserversocket
311 sslutil.wrapserversocket
319 except ImportError:
312 except ImportError:
320 raise error.Abort(_(b"SSL support is unavailable"))
313 raise error.Abort(_(b"SSL support is unavailable"))
321
314
322 certfile = ui.config(b'web', b'certificate')
315 certfile = ui.config(b'web', b'certificate')
323
316
324 # These config options are currently only meant for testing. Use
317 # These config options are currently only meant for testing. Use
325 # at your own risk.
318 # at your own risk.
326 cafile = ui.config(b'devel', b'servercafile')
319 cafile = ui.config(b'devel', b'servercafile')
327 reqcert = ui.configbool(b'devel', b'serverrequirecert')
320 reqcert = ui.configbool(b'devel', b'serverrequirecert')
328
321
329 httpserver.socket = sslutil.wrapserversocket(
322 httpserver.socket = sslutil.wrapserversocket(
330 httpserver.socket,
323 httpserver.socket,
331 ui,
324 ui,
332 certfile=certfile,
325 certfile=certfile,
333 cafile=cafile,
326 cafile=cafile,
334 requireclientcert=reqcert,
327 requireclientcert=reqcert,
335 )
328 )
336
329
337 def setup(self):
330 def setup(self):
338 self.connection = self.request
331 self.connection = self.request
339 self.rfile = self.request.makefile("rb", self.rbufsize)
332 self.rfile = self.request.makefile("rb", self.rbufsize)
340 self.wfile = self.request.makefile("wb", self.wbufsize)
333 self.wfile = self.request.makefile("wb", self.wbufsize)
341
334
342
335
343 try:
336 try:
344 import threading
337 import threading
345
338
346 threading.active_count() # silence pyflakes and bypass demandimport
339 threading.active_count() # silence pyflakes and bypass demandimport
347 _mixin = socketserver.ThreadingMixIn
340 _mixin = socketserver.ThreadingMixIn
348 except ImportError:
341 except ImportError:
349 if util.safehasattr(os, b"fork"):
342 if util.safehasattr(os, b"fork"):
350 _mixin = socketserver.ForkingMixIn
343 _mixin = socketserver.ForkingMixIn
351 else:
344 else:
352
345
353 class _mixin(object):
346 class _mixin(object):
354 pass
347 pass
355
348
356
349
357 def openlog(opt, default):
350 def openlog(opt, default):
358 if opt and opt != b'-':
351 if opt and opt != b'-':
359 return open(opt, b'ab')
352 return open(opt, b'ab')
360 return default
353 return default
361
354
362
355
363 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
356 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
364
357
365 # SO_REUSEADDR has broken semantics on windows
358 # SO_REUSEADDR has broken semantics on windows
366 if pycompat.iswindows:
359 if pycompat.iswindows:
367 allow_reuse_address = 0
360 allow_reuse_address = 0
368
361
369 def __init__(self, ui, app, addr, handler, **kwargs):
362 def __init__(self, ui, app, addr, handler, **kwargs):
370 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
363 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
371 self.daemon_threads = True
364 self.daemon_threads = True
372 self.application = app
365 self.application = app
373
366
374 handler.preparehttpserver(self, ui)
367 handler.preparehttpserver(self, ui)
375
368
376 prefix = ui.config(b'web', b'prefix')
369 prefix = ui.config(b'web', b'prefix')
377 if prefix:
370 if prefix:
378 prefix = b'/' + prefix.strip(b'/')
371 prefix = b'/' + prefix.strip(b'/')
379 self.prefix = prefix
372 self.prefix = prefix
380
373
381 alog = openlog(ui.config(b'web', b'accesslog'), ui.fout)
374 alog = openlog(ui.config(b'web', b'accesslog'), ui.fout)
382 elog = openlog(ui.config(b'web', b'errorlog'), ui.ferr)
375 elog = openlog(ui.config(b'web', b'errorlog'), ui.ferr)
383 self.accesslog = alog
376 self.accesslog = alog
384 self.errorlog = elog
377 self.errorlog = elog
385
378
386 self.addr, self.port = self.socket.getsockname()[0:2]
379 self.addr, self.port = self.socket.getsockname()[0:2]
387 self.fqaddr = self.server_name
380 self.fqaddr = self.server_name
388
381
389 self.serverheader = ui.config(b'web', b'server-header')
382 self.serverheader = ui.config(b'web', b'server-header')
390
383
391
384
392 class IPv6HTTPServer(MercurialHTTPServer):
385 class IPv6HTTPServer(MercurialHTTPServer):
393 address_family = getattr(socket, 'AF_INET6', None)
386 address_family = getattr(socket, 'AF_INET6', None)
394
387
395 def __init__(self, *args, **kwargs):
388 def __init__(self, *args, **kwargs):
396 if self.address_family is None:
389 if self.address_family is None:
397 raise error.RepoError(_(b'IPv6 is not available on this system'))
390 raise error.RepoError(_(b'IPv6 is not available on this system'))
398 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
391 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
399
392
400
393
401 def create_server(ui, app):
394 def create_server(ui, app):
402
395
403 if ui.config(b'web', b'certificate'):
396 if ui.config(b'web', b'certificate'):
404 handler = _httprequesthandlerssl
397 handler = _httprequesthandlerssl
405 else:
398 else:
406 handler = _httprequesthandler
399 handler = _httprequesthandler
407
400
408 if ui.configbool(b'web', b'ipv6'):
401 if ui.configbool(b'web', b'ipv6'):
409 cls = IPv6HTTPServer
402 cls = IPv6HTTPServer
410 else:
403 else:
411 cls = MercurialHTTPServer
404 cls = MercurialHTTPServer
412
405
413 # ugly hack due to python issue5853 (for threaded use)
406 # ugly hack due to python issue5853 (for threaded use)
414 try:
407 try:
415 import mimetypes
408 import mimetypes
416
409
417 mimetypes.init()
410 mimetypes.init()
418 except UnicodeDecodeError:
411 except UnicodeDecodeError:
419 # Python 2.x's mimetypes module attempts to decode strings
412 # Python 2.x's mimetypes module attempts to decode strings
420 # from Windows' ANSI APIs as ascii (fail), then re-encode them
413 # from Windows' ANSI APIs as ascii (fail), then re-encode them
421 # as ascii (clown fail), because the default Python Unicode
414 # as ascii (clown fail), because the default Python Unicode
422 # codec is hardcoded as ascii.
415 # codec is hardcoded as ascii.
423
416
424 sys.argv # unwrap demand-loader so that reload() works
417 sys.argv # unwrap demand-loader so that reload() works
425 # resurrect sys.setdefaultencoding()
418 # resurrect sys.setdefaultencoding()
426 try:
419 try:
427 importlib.reload(sys)
420 importlib.reload(sys)
428 except AttributeError:
421 except AttributeError:
429 reload(sys)
422 reload(sys)
430 oldenc = sys.getdefaultencoding()
423 oldenc = sys.getdefaultencoding()
431 sys.setdefaultencoding(b"latin1") # or any full 8-bit encoding
424 sys.setdefaultencoding(b"latin1") # or any full 8-bit encoding
432 mimetypes.init()
425 mimetypes.init()
433 sys.setdefaultencoding(oldenc)
426 sys.setdefaultencoding(oldenc)
434
427
435 address = ui.config(b'web', b'address')
428 address = ui.config(b'web', b'address')
436 port = urlutil.getport(ui.config(b'web', b'port'))
429 port = urlutil.getport(ui.config(b'web', b'port'))
437 try:
430 try:
438 return cls(ui, app, (address, port), handler)
431 return cls(ui, app, (address, port), handler)
439 except socket.error as inst:
432 except socket.error as inst:
440 raise error.Abort(
433 raise error.Abort(
441 _(b"cannot start server at '%s:%d': %s")
434 _(b"cannot start server at '%s:%d': %s")
442 % (address, port, encoding.strtolocal(inst.args[1]))
435 % (address, port, encoding.strtolocal(inst.args[1]))
443 )
436 )
General Comments 0
You need to be logged in to leave comments. Login now