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