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