##// END OF EJS Templates
hgweb: insist http_status value is a sysstr...
Augie Fackler -
r38317:af0e88e6 default
parent child Browse files
Show More
@@ -1,368 +1,369 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[0] != 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("500 Internal Server Error", [])
104 self._start_response("500 Internal Server Error", [])
105 self._write("Internal Server Error")
105 self._write("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 env[r'wsgi.multiprocess'] = isinstance(self.server,
177 env[r'wsgi.multiprocess'] = isinstance(self.server,
178 socketserver.ForkingMixIn)
178 socketserver.ForkingMixIn)
179 env[r'wsgi.run_once'] = 0
179 env[r'wsgi.run_once'] = 0
180
180
181 wsgiref.validate.check_environ(env)
181 wsgiref.validate.check_environ(env)
182
182
183 self.saved_status = None
183 self.saved_status = None
184 self.saved_headers = []
184 self.saved_headers = []
185 self.length = None
185 self.length = None
186 self._chunked = None
186 self._chunked = None
187 for chunk in self.server.application(env, self._start_response):
187 for chunk in self.server.application(env, self._start_response):
188 self._write(chunk)
188 self._write(chunk)
189 if not self.sent_headers:
189 if not self.sent_headers:
190 self.send_headers()
190 self.send_headers()
191 self._done()
191 self._done()
192
192
193 def send_headers(self):
193 def send_headers(self):
194 if not self.saved_status:
194 if not self.saved_status:
195 raise AssertionError("Sending headers before "
195 raise AssertionError("Sending headers before "
196 "start_response() called")
196 "start_response() called")
197 saved_status = self.saved_status.split(None, 1)
197 saved_status = self.saved_status.split(None, 1)
198 saved_status[0] = int(saved_status[0])
198 saved_status[0] = int(saved_status[0])
199 self.send_response(*saved_status)
199 self.send_response(*saved_status)
200 self.length = None
200 self.length = None
201 self._chunked = False
201 self._chunked = False
202 for h in self.saved_headers:
202 for h in self.saved_headers:
203 self.send_header(*h)
203 self.send_header(*h)
204 if h[0].lower() == 'content-length':
204 if h[0].lower() == 'content-length':
205 self.length = int(h[1])
205 self.length = int(h[1])
206 if (self.length is None and
206 if (self.length is None and
207 saved_status[0] != common.HTTP_NOT_MODIFIED):
207 saved_status[0] != common.HTTP_NOT_MODIFIED):
208 self._chunked = (not self.close_connection and
208 self._chunked = (not self.close_connection and
209 self.request_version == "HTTP/1.1")
209 self.request_version == "HTTP/1.1")
210 if self._chunked:
210 if self._chunked:
211 self.send_header(r'Transfer-Encoding', r'chunked')
211 self.send_header(r'Transfer-Encoding', r'chunked')
212 else:
212 else:
213 self.send_header(r'Connection', r'close')
213 self.send_header(r'Connection', r'close')
214 self.end_headers()
214 self.end_headers()
215 self.sent_headers = True
215 self.sent_headers = True
216
216
217 def _start_response(self, http_status, headers, exc_info=None):
217 def _start_response(self, http_status, headers, exc_info=None):
218 assert isinstance(http_status, str)
218 code, msg = http_status.split(None, 1)
219 code, msg = http_status.split(None, 1)
219 code = int(code)
220 code = int(code)
220 self.saved_status = http_status
221 self.saved_status = http_status
221 bad_headers = ('connection', 'transfer-encoding')
222 bad_headers = ('connection', 'transfer-encoding')
222 self.saved_headers = [h for h in headers
223 self.saved_headers = [h for h in headers
223 if h[0].lower() not in bad_headers]
224 if h[0].lower() not in bad_headers]
224 return self._write
225 return self._write
225
226
226 def _write(self, data):
227 def _write(self, data):
227 if not self.saved_status:
228 if not self.saved_status:
228 raise AssertionError("data written before start_response() called")
229 raise AssertionError("data written before start_response() called")
229 elif not self.sent_headers:
230 elif not self.sent_headers:
230 self.send_headers()
231 self.send_headers()
231 if self.length is not None:
232 if self.length is not None:
232 if len(data) > self.length:
233 if len(data) > self.length:
233 raise AssertionError("Content-length header sent, but more "
234 raise AssertionError("Content-length header sent, but more "
234 "bytes than specified are being written.")
235 "bytes than specified are being written.")
235 self.length = self.length - len(data)
236 self.length = self.length - len(data)
236 elif self._chunked and data:
237 elif self._chunked and data:
237 data = '%x\r\n%s\r\n' % (len(data), data)
238 data = '%x\r\n%s\r\n' % (len(data), data)
238 self.wfile.write(data)
239 self.wfile.write(data)
239 self.wfile.flush()
240 self.wfile.flush()
240
241
241 def _done(self):
242 def _done(self):
242 if self._chunked:
243 if self._chunked:
243 self.wfile.write('0\r\n\r\n')
244 self.wfile.write('0\r\n\r\n')
244 self.wfile.flush()
245 self.wfile.flush()
245
246
246 def version_string(self):
247 def version_string(self):
247 if self.server.serverheader:
248 if self.server.serverheader:
248 return self.server.serverheader
249 return self.server.serverheader
249 return httpservermod.basehttprequesthandler.version_string(self)
250 return httpservermod.basehttprequesthandler.version_string(self)
250
251
251 class _httprequesthandlerssl(_httprequesthandler):
252 class _httprequesthandlerssl(_httprequesthandler):
252 """HTTPS handler based on Python's ssl module"""
253 """HTTPS handler based on Python's ssl module"""
253
254
254 url_scheme = 'https'
255 url_scheme = 'https'
255
256
256 @staticmethod
257 @staticmethod
257 def preparehttpserver(httpserver, ui):
258 def preparehttpserver(httpserver, ui):
258 try:
259 try:
259 from .. import sslutil
260 from .. import sslutil
260 sslutil.modernssl
261 sslutil.modernssl
261 except ImportError:
262 except ImportError:
262 raise error.Abort(_("SSL support is unavailable"))
263 raise error.Abort(_("SSL support is unavailable"))
263
264
264 certfile = ui.config('web', 'certificate')
265 certfile = ui.config('web', 'certificate')
265
266
266 # These config options are currently only meant for testing. Use
267 # These config options are currently only meant for testing. Use
267 # at your own risk.
268 # at your own risk.
268 cafile = ui.config('devel', 'servercafile')
269 cafile = ui.config('devel', 'servercafile')
269 reqcert = ui.configbool('devel', 'serverrequirecert')
270 reqcert = ui.configbool('devel', 'serverrequirecert')
270
271
271 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
272 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
272 ui,
273 ui,
273 certfile=certfile,
274 certfile=certfile,
274 cafile=cafile,
275 cafile=cafile,
275 requireclientcert=reqcert)
276 requireclientcert=reqcert)
276
277
277 def setup(self):
278 def setup(self):
278 self.connection = self.request
279 self.connection = self.request
279 self.rfile = self.request.makefile(r"rb", self.rbufsize)
280 self.rfile = self.request.makefile(r"rb", self.rbufsize)
280 self.wfile = self.request.makefile(r"wb", self.wbufsize)
281 self.wfile = self.request.makefile(r"wb", self.wbufsize)
281
282
282 try:
283 try:
283 import threading
284 import threading
284 threading.activeCount() # silence pyflakes and bypass demandimport
285 threading.activeCount() # silence pyflakes and bypass demandimport
285 _mixin = socketserver.ThreadingMixIn
286 _mixin = socketserver.ThreadingMixIn
286 except ImportError:
287 except ImportError:
287 if util.safehasattr(os, "fork"):
288 if util.safehasattr(os, "fork"):
288 _mixin = socketserver.ForkingMixIn
289 _mixin = socketserver.ForkingMixIn
289 else:
290 else:
290 class _mixin(object):
291 class _mixin(object):
291 pass
292 pass
292
293
293 def openlog(opt, default):
294 def openlog(opt, default):
294 if opt and opt != '-':
295 if opt and opt != '-':
295 return open(opt, 'ab')
296 return open(opt, 'ab')
296 return default
297 return default
297
298
298 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
299 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
299
300
300 # SO_REUSEADDR has broken semantics on windows
301 # SO_REUSEADDR has broken semantics on windows
301 if pycompat.iswindows:
302 if pycompat.iswindows:
302 allow_reuse_address = 0
303 allow_reuse_address = 0
303
304
304 def __init__(self, ui, app, addr, handler, **kwargs):
305 def __init__(self, ui, app, addr, handler, **kwargs):
305 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
306 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
306 self.daemon_threads = True
307 self.daemon_threads = True
307 self.application = app
308 self.application = app
308
309
309 handler.preparehttpserver(self, ui)
310 handler.preparehttpserver(self, ui)
310
311
311 prefix = ui.config('web', 'prefix')
312 prefix = ui.config('web', 'prefix')
312 if prefix:
313 if prefix:
313 prefix = '/' + prefix.strip('/')
314 prefix = '/' + prefix.strip('/')
314 self.prefix = prefix
315 self.prefix = prefix
315
316
316 alog = openlog(ui.config('web', 'accesslog'), ui.fout)
317 alog = openlog(ui.config('web', 'accesslog'), ui.fout)
317 elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
318 elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
318 self.accesslog = alog
319 self.accesslog = alog
319 self.errorlog = elog
320 self.errorlog = elog
320
321
321 self.addr, self.port = self.socket.getsockname()[0:2]
322 self.addr, self.port = self.socket.getsockname()[0:2]
322 self.fqaddr = socket.getfqdn(addr[0])
323 self.fqaddr = socket.getfqdn(addr[0])
323
324
324 self.serverheader = ui.config('web', 'server-header')
325 self.serverheader = ui.config('web', 'server-header')
325
326
326 class IPv6HTTPServer(MercurialHTTPServer):
327 class IPv6HTTPServer(MercurialHTTPServer):
327 address_family = getattr(socket, 'AF_INET6', None)
328 address_family = getattr(socket, 'AF_INET6', None)
328 def __init__(self, *args, **kwargs):
329 def __init__(self, *args, **kwargs):
329 if self.address_family is None:
330 if self.address_family is None:
330 raise error.RepoError(_('IPv6 is not available on this system'))
331 raise error.RepoError(_('IPv6 is not available on this system'))
331 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
332 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
332
333
333 def create_server(ui, app):
334 def create_server(ui, app):
334
335
335 if ui.config('web', 'certificate'):
336 if ui.config('web', 'certificate'):
336 handler = _httprequesthandlerssl
337 handler = _httprequesthandlerssl
337 else:
338 else:
338 handler = _httprequesthandler
339 handler = _httprequesthandler
339
340
340 if ui.configbool('web', 'ipv6'):
341 if ui.configbool('web', 'ipv6'):
341 cls = IPv6HTTPServer
342 cls = IPv6HTTPServer
342 else:
343 else:
343 cls = MercurialHTTPServer
344 cls = MercurialHTTPServer
344
345
345 # ugly hack due to python issue5853 (for threaded use)
346 # ugly hack due to python issue5853 (for threaded use)
346 try:
347 try:
347 import mimetypes
348 import mimetypes
348 mimetypes.init()
349 mimetypes.init()
349 except UnicodeDecodeError:
350 except UnicodeDecodeError:
350 # Python 2.x's mimetypes module attempts to decode strings
351 # Python 2.x's mimetypes module attempts to decode strings
351 # from Windows' ANSI APIs as ascii (fail), then re-encode them
352 # from Windows' ANSI APIs as ascii (fail), then re-encode them
352 # as ascii (clown fail), because the default Python Unicode
353 # as ascii (clown fail), because the default Python Unicode
353 # codec is hardcoded as ascii.
354 # codec is hardcoded as ascii.
354
355
355 sys.argv # unwrap demand-loader so that reload() works
356 sys.argv # unwrap demand-loader so that reload() works
356 reload(sys) # resurrect sys.setdefaultencoding()
357 reload(sys) # resurrect sys.setdefaultencoding()
357 oldenc = sys.getdefaultencoding()
358 oldenc = sys.getdefaultencoding()
358 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
359 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
359 mimetypes.init()
360 mimetypes.init()
360 sys.setdefaultencoding(oldenc)
361 sys.setdefaultencoding(oldenc)
361
362
362 address = ui.config('web', 'address')
363 address = ui.config('web', 'address')
363 port = util.getport(ui.config('web', 'port'))
364 port = util.getport(ui.config('web', 'port'))
364 try:
365 try:
365 return cls(ui, app, (address, port), handler)
366 return cls(ui, app, (address, port), handler)
366 except socket.error as inst:
367 except socket.error as inst:
367 raise error.Abort(_("cannot start server at '%s:%d': %s")
368 raise error.Abort(_("cannot start server at '%s:%d': %s")
368 % (address, port, encoding.strtolocal(inst.args[1])))
369 % (address, port, encoding.strtolocal(inst.args[1])))
General Comments 0
You need to be logged in to leave comments. Login now