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