##// END OF EJS Templates
hgweb.server: fix _httprequesthandlerssl help text
timeless@mozdev.org -
r26202:04af4300 default
parent child Browse files
Show More
@@ -1,349 +1,349 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 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
10 from mercurial import util, error
10 from mercurial import util, error
11 from mercurial.hgweb import common
11 from mercurial.hgweb import common
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13
13
14 def _splitURI(uri):
14 def _splitURI(uri):
15 """Return path and query that has been split from uri
15 """Return path and query that has been split from uri
16
16
17 Just like CGI environment, the path is unquoted, the query is
17 Just like CGI environment, the path is unquoted, the query is
18 not.
18 not.
19 """
19 """
20 if '?' in uri:
20 if '?' in uri:
21 path, query = uri.split('?', 1)
21 path, query = uri.split('?', 1)
22 else:
22 else:
23 path, query = uri, ''
23 path, query = uri, ''
24 return urllib.unquote(path), query
24 return urllib.unquote(path), query
25
25
26 class _error_logger(object):
26 class _error_logger(object):
27 def __init__(self, handler):
27 def __init__(self, handler):
28 self.handler = handler
28 self.handler = handler
29 def flush(self):
29 def flush(self):
30 pass
30 pass
31 def write(self, str):
31 def write(self, str):
32 self.writelines(str.split('\n'))
32 self.writelines(str.split('\n'))
33 def writelines(self, seq):
33 def writelines(self, seq):
34 for msg in seq:
34 for msg in seq:
35 self.handler.log_error("HG error: %s", msg)
35 self.handler.log_error("HG error: %s", msg)
36
36
37 class _httprequesthandler(BaseHTTPServer.BaseHTTPRequestHandler):
37 class _httprequesthandler(BaseHTTPServer.BaseHTTPRequestHandler):
38
38
39 url_scheme = 'http'
39 url_scheme = 'http'
40
40
41 @staticmethod
41 @staticmethod
42 def preparehttpserver(httpserver, ssl_cert):
42 def preparehttpserver(httpserver, ssl_cert):
43 """Prepare .socket of new HTTPServer instance"""
43 """Prepare .socket of new HTTPServer instance"""
44 pass
44 pass
45
45
46 def __init__(self, *args, **kargs):
46 def __init__(self, *args, **kargs):
47 self.protocol_version = 'HTTP/1.1'
47 self.protocol_version = 'HTTP/1.1'
48 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
48 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
49
49
50 def _log_any(self, fp, format, *args):
50 def _log_any(self, fp, format, *args):
51 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
51 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
52 self.log_date_time_string(),
52 self.log_date_time_string(),
53 format % args))
53 format % args))
54 fp.flush()
54 fp.flush()
55
55
56 def log_error(self, format, *args):
56 def log_error(self, format, *args):
57 self._log_any(self.server.errorlog, format, *args)
57 self._log_any(self.server.errorlog, format, *args)
58
58
59 def log_message(self, format, *args):
59 def log_message(self, format, *args):
60 self._log_any(self.server.accesslog, format, *args)
60 self._log_any(self.server.accesslog, format, *args)
61
61
62 def log_request(self, code='-', size='-'):
62 def log_request(self, code='-', size='-'):
63 xheaders = []
63 xheaders = []
64 if util.safehasattr(self, 'headers'):
64 if util.safehasattr(self, 'headers'):
65 xheaders = [h for h in self.headers.items()
65 xheaders = [h for h in self.headers.items()
66 if h[0].startswith('x-')]
66 if h[0].startswith('x-')]
67 self.log_message('"%s" %s %s%s',
67 self.log_message('"%s" %s %s%s',
68 self.requestline, str(code), str(size),
68 self.requestline, str(code), str(size),
69 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
69 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
70
70
71 def do_write(self):
71 def do_write(self):
72 try:
72 try:
73 self.do_hgweb()
73 self.do_hgweb()
74 except socket.error as inst:
74 except socket.error as inst:
75 if inst[0] != errno.EPIPE:
75 if inst[0] != errno.EPIPE:
76 raise
76 raise
77
77
78 def do_POST(self):
78 def do_POST(self):
79 try:
79 try:
80 self.do_write()
80 self.do_write()
81 except Exception:
81 except Exception:
82 self._start_response("500 Internal Server Error", [])
82 self._start_response("500 Internal Server Error", [])
83 self._write("Internal Server Error")
83 self._write("Internal Server Error")
84 self._done()
84 self._done()
85 tb = "".join(traceback.format_exception(*sys.exc_info()))
85 tb = "".join(traceback.format_exception(*sys.exc_info()))
86 self.log_error("Exception happened during processing "
86 self.log_error("Exception happened during processing "
87 "request '%s':\n%s", self.path, tb)
87 "request '%s':\n%s", self.path, tb)
88
88
89 def do_GET(self):
89 def do_GET(self):
90 self.do_POST()
90 self.do_POST()
91
91
92 def do_hgweb(self):
92 def do_hgweb(self):
93 path, query = _splitURI(self.path)
93 path, query = _splitURI(self.path)
94
94
95 env = {}
95 env = {}
96 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
96 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
97 env['REQUEST_METHOD'] = self.command
97 env['REQUEST_METHOD'] = self.command
98 env['SERVER_NAME'] = self.server.server_name
98 env['SERVER_NAME'] = self.server.server_name
99 env['SERVER_PORT'] = str(self.server.server_port)
99 env['SERVER_PORT'] = str(self.server.server_port)
100 env['REQUEST_URI'] = self.path
100 env['REQUEST_URI'] = self.path
101 env['SCRIPT_NAME'] = self.server.prefix
101 env['SCRIPT_NAME'] = self.server.prefix
102 env['PATH_INFO'] = path[len(self.server.prefix):]
102 env['PATH_INFO'] = path[len(self.server.prefix):]
103 env['REMOTE_HOST'] = self.client_address[0]
103 env['REMOTE_HOST'] = self.client_address[0]
104 env['REMOTE_ADDR'] = self.client_address[0]
104 env['REMOTE_ADDR'] = self.client_address[0]
105 if query:
105 if query:
106 env['QUERY_STRING'] = query
106 env['QUERY_STRING'] = query
107
107
108 if self.headers.typeheader is None:
108 if self.headers.typeheader is None:
109 env['CONTENT_TYPE'] = self.headers.type
109 env['CONTENT_TYPE'] = self.headers.type
110 else:
110 else:
111 env['CONTENT_TYPE'] = self.headers.typeheader
111 env['CONTENT_TYPE'] = self.headers.typeheader
112 length = self.headers.getheader('content-length')
112 length = self.headers.getheader('content-length')
113 if length:
113 if length:
114 env['CONTENT_LENGTH'] = length
114 env['CONTENT_LENGTH'] = length
115 for header in [h for h in self.headers.keys()
115 for header in [h for h in self.headers.keys()
116 if h not in ('content-type', 'content-length')]:
116 if h not in ('content-type', 'content-length')]:
117 hkey = 'HTTP_' + header.replace('-', '_').upper()
117 hkey = 'HTTP_' + header.replace('-', '_').upper()
118 hval = self.headers.getheader(header)
118 hval = self.headers.getheader(header)
119 hval = hval.replace('\n', '').strip()
119 hval = hval.replace('\n', '').strip()
120 if hval:
120 if hval:
121 env[hkey] = hval
121 env[hkey] = hval
122 env['SERVER_PROTOCOL'] = self.request_version
122 env['SERVER_PROTOCOL'] = self.request_version
123 env['wsgi.version'] = (1, 0)
123 env['wsgi.version'] = (1, 0)
124 env['wsgi.url_scheme'] = self.url_scheme
124 env['wsgi.url_scheme'] = self.url_scheme
125 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
125 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
126 self.rfile = common.continuereader(self.rfile, self.wfile.write)
126 self.rfile = common.continuereader(self.rfile, self.wfile.write)
127
127
128 env['wsgi.input'] = self.rfile
128 env['wsgi.input'] = self.rfile
129 env['wsgi.errors'] = _error_logger(self)
129 env['wsgi.errors'] = _error_logger(self)
130 env['wsgi.multithread'] = isinstance(self.server,
130 env['wsgi.multithread'] = isinstance(self.server,
131 SocketServer.ThreadingMixIn)
131 SocketServer.ThreadingMixIn)
132 env['wsgi.multiprocess'] = isinstance(self.server,
132 env['wsgi.multiprocess'] = isinstance(self.server,
133 SocketServer.ForkingMixIn)
133 SocketServer.ForkingMixIn)
134 env['wsgi.run_once'] = 0
134 env['wsgi.run_once'] = 0
135
135
136 self.saved_status = None
136 self.saved_status = None
137 self.saved_headers = []
137 self.saved_headers = []
138 self.sent_headers = False
138 self.sent_headers = False
139 self.length = None
139 self.length = None
140 self._chunked = None
140 self._chunked = None
141 for chunk in self.server.application(env, self._start_response):
141 for chunk in self.server.application(env, self._start_response):
142 self._write(chunk)
142 self._write(chunk)
143 if not self.sent_headers:
143 if not self.sent_headers:
144 self.send_headers()
144 self.send_headers()
145 self._done()
145 self._done()
146
146
147 def send_headers(self):
147 def send_headers(self):
148 if not self.saved_status:
148 if not self.saved_status:
149 raise AssertionError("Sending headers before "
149 raise AssertionError("Sending headers before "
150 "start_response() called")
150 "start_response() called")
151 saved_status = self.saved_status.split(None, 1)
151 saved_status = self.saved_status.split(None, 1)
152 saved_status[0] = int(saved_status[0])
152 saved_status[0] = int(saved_status[0])
153 self.send_response(*saved_status)
153 self.send_response(*saved_status)
154 self.length = None
154 self.length = None
155 self._chunked = False
155 self._chunked = False
156 for h in self.saved_headers:
156 for h in self.saved_headers:
157 self.send_header(*h)
157 self.send_header(*h)
158 if h[0].lower() == 'content-length':
158 if h[0].lower() == 'content-length':
159 self.length = int(h[1])
159 self.length = int(h[1])
160 if (self.length is None and
160 if (self.length is None and
161 saved_status[0] != common.HTTP_NOT_MODIFIED):
161 saved_status[0] != common.HTTP_NOT_MODIFIED):
162 self._chunked = (not self.close_connection and
162 self._chunked = (not self.close_connection and
163 self.request_version == "HTTP/1.1")
163 self.request_version == "HTTP/1.1")
164 if self._chunked:
164 if self._chunked:
165 self.send_header('Transfer-Encoding', 'chunked')
165 self.send_header('Transfer-Encoding', 'chunked')
166 else:
166 else:
167 self.send_header('Connection', 'close')
167 self.send_header('Connection', 'close')
168 self.end_headers()
168 self.end_headers()
169 self.sent_headers = True
169 self.sent_headers = True
170
170
171 def _start_response(self, http_status, headers, exc_info=None):
171 def _start_response(self, http_status, headers, exc_info=None):
172 code, msg = http_status.split(None, 1)
172 code, msg = http_status.split(None, 1)
173 code = int(code)
173 code = int(code)
174 self.saved_status = http_status
174 self.saved_status = http_status
175 bad_headers = ('connection', 'transfer-encoding')
175 bad_headers = ('connection', 'transfer-encoding')
176 self.saved_headers = [h for h in headers
176 self.saved_headers = [h for h in headers
177 if h[0].lower() not in bad_headers]
177 if h[0].lower() not in bad_headers]
178 return self._write
178 return self._write
179
179
180 def _write(self, data):
180 def _write(self, data):
181 if not self.saved_status:
181 if not self.saved_status:
182 raise AssertionError("data written before start_response() called")
182 raise AssertionError("data written before start_response() called")
183 elif not self.sent_headers:
183 elif not self.sent_headers:
184 self.send_headers()
184 self.send_headers()
185 if self.length is not None:
185 if self.length is not None:
186 if len(data) > self.length:
186 if len(data) > self.length:
187 raise AssertionError("Content-length header sent, but more "
187 raise AssertionError("Content-length header sent, but more "
188 "bytes than specified are being written.")
188 "bytes than specified are being written.")
189 self.length = self.length - len(data)
189 self.length = self.length - len(data)
190 elif self._chunked and data:
190 elif self._chunked and data:
191 data = '%x\r\n%s\r\n' % (len(data), data)
191 data = '%x\r\n%s\r\n' % (len(data), data)
192 self.wfile.write(data)
192 self.wfile.write(data)
193 self.wfile.flush()
193 self.wfile.flush()
194
194
195 def _done(self):
195 def _done(self):
196 if self._chunked:
196 if self._chunked:
197 self.wfile.write('0\r\n\r\n')
197 self.wfile.write('0\r\n\r\n')
198 self.wfile.flush()
198 self.wfile.flush()
199
199
200 class _httprequesthandleropenssl(_httprequesthandler):
200 class _httprequesthandleropenssl(_httprequesthandler):
201 """HTTPS handler based on pyOpenSSL"""
201 """HTTPS handler based on pyOpenSSL"""
202
202
203 url_scheme = 'https'
203 url_scheme = 'https'
204
204
205 @staticmethod
205 @staticmethod
206 def preparehttpserver(httpserver, ssl_cert):
206 def preparehttpserver(httpserver, ssl_cert):
207 try:
207 try:
208 import OpenSSL
208 import OpenSSL
209 OpenSSL.SSL.Context
209 OpenSSL.SSL.Context
210 except ImportError:
210 except ImportError:
211 raise util.Abort(_("SSL support is unavailable"))
211 raise util.Abort(_("SSL support is unavailable"))
212 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
212 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
213 ctx.use_privatekey_file(ssl_cert)
213 ctx.use_privatekey_file(ssl_cert)
214 ctx.use_certificate_file(ssl_cert)
214 ctx.use_certificate_file(ssl_cert)
215 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
215 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
216 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
216 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
217 httpserver.server_bind()
217 httpserver.server_bind()
218 httpserver.server_activate()
218 httpserver.server_activate()
219
219
220 def setup(self):
220 def setup(self):
221 self.connection = self.request
221 self.connection = self.request
222 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
222 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
223 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
223 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
224
224
225 def do_write(self):
225 def do_write(self):
226 import OpenSSL
226 import OpenSSL
227 try:
227 try:
228 _httprequesthandler.do_write(self)
228 _httprequesthandler.do_write(self)
229 except OpenSSL.SSL.SysCallError as inst:
229 except OpenSSL.SSL.SysCallError as inst:
230 if inst.args[0] != errno.EPIPE:
230 if inst.args[0] != errno.EPIPE:
231 raise
231 raise
232
232
233 def handle_one_request(self):
233 def handle_one_request(self):
234 import OpenSSL
234 import OpenSSL
235 try:
235 try:
236 _httprequesthandler.handle_one_request(self)
236 _httprequesthandler.handle_one_request(self)
237 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
237 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
238 self.close_connection = True
238 self.close_connection = True
239 pass
239 pass
240
240
241 class _httprequesthandlerssl(_httprequesthandler):
241 class _httprequesthandlerssl(_httprequesthandler):
242 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
242 """HTTPS handler based on Python's ssl module"""
243
243
244 url_scheme = 'https'
244 url_scheme = 'https'
245
245
246 @staticmethod
246 @staticmethod
247 def preparehttpserver(httpserver, ssl_cert):
247 def preparehttpserver(httpserver, ssl_cert):
248 try:
248 try:
249 import ssl
249 import ssl
250 ssl.wrap_socket
250 ssl.wrap_socket
251 except ImportError:
251 except ImportError:
252 raise util.Abort(_("SSL support is unavailable"))
252 raise util.Abort(_("SSL support is unavailable"))
253 httpserver.socket = ssl.wrap_socket(
253 httpserver.socket = ssl.wrap_socket(
254 httpserver.socket, server_side=True,
254 httpserver.socket, server_side=True,
255 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_TLSv1)
255 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_TLSv1)
256
256
257 def setup(self):
257 def setup(self):
258 self.connection = self.request
258 self.connection = self.request
259 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
259 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
260 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
260 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
261
261
262 try:
262 try:
263 from threading import activeCount
263 from threading import activeCount
264 activeCount() # silence pyflakes
264 activeCount() # silence pyflakes
265 _mixin = SocketServer.ThreadingMixIn
265 _mixin = SocketServer.ThreadingMixIn
266 except ImportError:
266 except ImportError:
267 if util.safehasattr(os, "fork"):
267 if util.safehasattr(os, "fork"):
268 _mixin = SocketServer.ForkingMixIn
268 _mixin = SocketServer.ForkingMixIn
269 else:
269 else:
270 class _mixin(object):
270 class _mixin(object):
271 pass
271 pass
272
272
273 def openlog(opt, default):
273 def openlog(opt, default):
274 if opt and opt != '-':
274 if opt and opt != '-':
275 return open(opt, 'a')
275 return open(opt, 'a')
276 return default
276 return default
277
277
278 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
278 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
279
279
280 # SO_REUSEADDR has broken semantics on windows
280 # SO_REUSEADDR has broken semantics on windows
281 if os.name == 'nt':
281 if os.name == 'nt':
282 allow_reuse_address = 0
282 allow_reuse_address = 0
283
283
284 def __init__(self, ui, app, addr, handler, **kwargs):
284 def __init__(self, ui, app, addr, handler, **kwargs):
285 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
285 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
286 self.daemon_threads = True
286 self.daemon_threads = True
287 self.application = app
287 self.application = app
288
288
289 handler.preparehttpserver(self, ui.config('web', 'certificate'))
289 handler.preparehttpserver(self, ui.config('web', 'certificate'))
290
290
291 prefix = ui.config('web', 'prefix', '')
291 prefix = ui.config('web', 'prefix', '')
292 if prefix:
292 if prefix:
293 prefix = '/' + prefix.strip('/')
293 prefix = '/' + prefix.strip('/')
294 self.prefix = prefix
294 self.prefix = prefix
295
295
296 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
296 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
297 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
297 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
298 self.accesslog = alog
298 self.accesslog = alog
299 self.errorlog = elog
299 self.errorlog = elog
300
300
301 self.addr, self.port = self.socket.getsockname()[0:2]
301 self.addr, self.port = self.socket.getsockname()[0:2]
302 self.fqaddr = socket.getfqdn(addr[0])
302 self.fqaddr = socket.getfqdn(addr[0])
303
303
304 class IPv6HTTPServer(MercurialHTTPServer):
304 class IPv6HTTPServer(MercurialHTTPServer):
305 address_family = getattr(socket, 'AF_INET6', None)
305 address_family = getattr(socket, 'AF_INET6', None)
306 def __init__(self, *args, **kwargs):
306 def __init__(self, *args, **kwargs):
307 if self.address_family is None:
307 if self.address_family is None:
308 raise error.RepoError(_('IPv6 is not available on this system'))
308 raise error.RepoError(_('IPv6 is not available on this system'))
309 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
309 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
310
310
311 def create_server(ui, app):
311 def create_server(ui, app):
312
312
313 if ui.config('web', 'certificate'):
313 if ui.config('web', 'certificate'):
314 if sys.version_info >= (2, 6):
314 if sys.version_info >= (2, 6):
315 handler = _httprequesthandlerssl
315 handler = _httprequesthandlerssl
316 else:
316 else:
317 handler = _httprequesthandleropenssl
317 handler = _httprequesthandleropenssl
318 else:
318 else:
319 handler = _httprequesthandler
319 handler = _httprequesthandler
320
320
321 if ui.configbool('web', 'ipv6'):
321 if ui.configbool('web', 'ipv6'):
322 cls = IPv6HTTPServer
322 cls = IPv6HTTPServer
323 else:
323 else:
324 cls = MercurialHTTPServer
324 cls = MercurialHTTPServer
325
325
326 # ugly hack due to python issue5853 (for threaded use)
326 # ugly hack due to python issue5853 (for threaded use)
327 try:
327 try:
328 import mimetypes
328 import mimetypes
329 mimetypes.init()
329 mimetypes.init()
330 except UnicodeDecodeError:
330 except UnicodeDecodeError:
331 # Python 2.x's mimetypes module attempts to decode strings
331 # Python 2.x's mimetypes module attempts to decode strings
332 # from Windows' ANSI APIs as ascii (fail), then re-encode them
332 # from Windows' ANSI APIs as ascii (fail), then re-encode them
333 # as ascii (clown fail), because the default Python Unicode
333 # as ascii (clown fail), because the default Python Unicode
334 # codec is hardcoded as ascii.
334 # codec is hardcoded as ascii.
335
335
336 sys.argv # unwrap demand-loader so that reload() works
336 sys.argv # unwrap demand-loader so that reload() works
337 reload(sys) # resurrect sys.setdefaultencoding()
337 reload(sys) # resurrect sys.setdefaultencoding()
338 oldenc = sys.getdefaultencoding()
338 oldenc = sys.getdefaultencoding()
339 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
339 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
340 mimetypes.init()
340 mimetypes.init()
341 sys.setdefaultencoding(oldenc)
341 sys.setdefaultencoding(oldenc)
342
342
343 address = ui.config('web', 'address', '')
343 address = ui.config('web', 'address', '')
344 port = util.getport(ui.config('web', 'port', 8000))
344 port = util.getport(ui.config('web', 'port', 8000))
345 try:
345 try:
346 return cls(ui, app, (address, port), handler)
346 return cls(ui, app, (address, port), handler)
347 except socket.error as inst:
347 except socket.error as inst:
348 raise util.Abort(_("cannot start server at '%s:%d': %s")
348 raise util.Abort(_("cannot start server at '%s:%d': %s")
349 % (address, port, inst.args[1]))
349 % (address, port, inst.args[1]))
General Comments 0
You need to be logged in to leave comments. Login now