##// END OF EJS Templates
py3: fix crash when server address is 0.0.0.0 (issue6362)...
Manuel Jacob -
r45578:b1a17022 stable
parent child Browse files
Show More
@@ -1,441 +1,441 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 importlib
12 import importlib
13 import os
13 import os
14 import socket
14 import socket
15 import sys
15 import sys
16 import traceback
16 import traceback
17 import wsgiref.validate
17 import wsgiref.validate
18
18
19 from ..i18n import _
19 from ..i18n import _
20 from ..pycompat import (
20 from ..pycompat import (
21 getattr,
21 getattr,
22 open,
22 open,
23 )
23 )
24
24
25 from .. import (
25 from .. import (
26 encoding,
26 encoding,
27 error,
27 error,
28 pycompat,
28 pycompat,
29 util,
29 util,
30 )
30 )
31
31
32 httpservermod = util.httpserver
32 httpservermod = util.httpserver
33 socketserver = util.socketserver
33 socketserver = util.socketserver
34 urlerr = util.urlerr
34 urlerr = util.urlerr
35 urlreq = util.urlreq
35 urlreq = util.urlreq
36
36
37 from . import common
37 from . import common
38
38
39
39
40 def _splitURI(uri):
40 def _splitURI(uri):
41 """Return path and query that has been split from uri
41 """Return path and query that has been split from uri
42
42
43 Just like CGI environment, the path is unquoted, the query is
43 Just like CGI environment, the path is unquoted, the query is
44 not.
44 not.
45 """
45 """
46 if '?' in uri:
46 if '?' in uri:
47 path, query = uri.split('?', 1)
47 path, query = uri.split('?', 1)
48 else:
48 else:
49 path, query = uri, r''
49 path, query = uri, r''
50 return urlreq.unquote(path), query
50 return urlreq.unquote(path), query
51
51
52
52
53 class _error_logger(object):
53 class _error_logger(object):
54 def __init__(self, handler):
54 def __init__(self, handler):
55 self.handler = handler
55 self.handler = handler
56
56
57 def flush(self):
57 def flush(self):
58 pass
58 pass
59
59
60 def write(self, str):
60 def write(self, str):
61 self.writelines(str.split(b'\n'))
61 self.writelines(str.split(b'\n'))
62
62
63 def writelines(self, seq):
63 def writelines(self, seq):
64 for msg in seq:
64 for msg in seq:
65 self.handler.log_error("HG error: %s", encoding.strfromlocal(msg))
65 self.handler.log_error("HG error: %s", encoding.strfromlocal(msg))
66
66
67
67
68 class _httprequesthandler(httpservermod.basehttprequesthandler):
68 class _httprequesthandler(httpservermod.basehttprequesthandler):
69
69
70 url_scheme = b'http'
70 url_scheme = b'http'
71
71
72 @staticmethod
72 @staticmethod
73 def preparehttpserver(httpserver, ui):
73 def preparehttpserver(httpserver, ui):
74 """Prepare .socket of new HTTPServer instance"""
74 """Prepare .socket of new HTTPServer instance"""
75
75
76 def __init__(self, *args, **kargs):
76 def __init__(self, *args, **kargs):
77 self.protocol_version = r'HTTP/1.1'
77 self.protocol_version = r'HTTP/1.1'
78 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
78 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
79
79
80 def _log_any(self, fp, format, *args):
80 def _log_any(self, fp, format, *args):
81 fp.write(
81 fp.write(
82 pycompat.sysbytes(
82 pycompat.sysbytes(
83 r"%s - - [%s] %s"
83 r"%s - - [%s] %s"
84 % (
84 % (
85 self.client_address[0],
85 self.client_address[0],
86 self.log_date_time_string(),
86 self.log_date_time_string(),
87 format % args,
87 format % args,
88 )
88 )
89 )
89 )
90 + b'\n'
90 + b'\n'
91 )
91 )
92 fp.flush()
92 fp.flush()
93
93
94 def log_error(self, format, *args):
94 def log_error(self, format, *args):
95 self._log_any(self.server.errorlog, format, *args)
95 self._log_any(self.server.errorlog, format, *args)
96
96
97 def log_message(self, format, *args):
97 def log_message(self, format, *args):
98 self._log_any(self.server.accesslog, format, *args)
98 self._log_any(self.server.accesslog, format, *args)
99
99
100 def log_request(self, code='-', size='-'):
100 def log_request(self, code='-', size='-'):
101 xheaders = []
101 xheaders = []
102 if util.safehasattr(self, b'headers'):
102 if util.safehasattr(self, b'headers'):
103 xheaders = [
103 xheaders = [
104 h for h in self.headers.items() if h[0].startswith('x-')
104 h for h in self.headers.items() if h[0].startswith('x-')
105 ]
105 ]
106 self.log_message(
106 self.log_message(
107 '"%s" %s %s%s',
107 '"%s" %s %s%s',
108 self.requestline,
108 self.requestline,
109 str(code),
109 str(code),
110 str(size),
110 str(size),
111 ''.join([' %s:%s' % h for h in sorted(xheaders)]),
111 ''.join([' %s:%s' % h for h in sorted(xheaders)]),
112 )
112 )
113
113
114 def do_write(self):
114 def do_write(self):
115 try:
115 try:
116 self.do_hgweb()
116 self.do_hgweb()
117 except socket.error as inst:
117 except socket.error as inst:
118 if inst.errno != errno.EPIPE:
118 if inst.errno != errno.EPIPE:
119 raise
119 raise
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_hgweb(self):
154 def do_hgweb(self):
155 self.sent_headers = False
155 self.sent_headers = False
156 path, query = _splitURI(self.path)
156 path, query = _splitURI(self.path)
157
157
158 # Ensure the slicing of path below is valid
158 # Ensure the slicing of path below is valid
159 if path != self.server.prefix and not path.startswith(
159 if path != self.server.prefix and not path.startswith(
160 self.server.prefix + b'/'
160 self.server.prefix + b'/'
161 ):
161 ):
162 self._start_response(pycompat.strurl(common.statusmessage(404)), [])
162 self._start_response(pycompat.strurl(common.statusmessage(404)), [])
163 if self.command == 'POST':
163 if self.command == 'POST':
164 # Paranoia: tell the client we're going to close the
164 # Paranoia: tell the client we're going to close the
165 # socket so they don't try and reuse a socket that
165 # socket so they don't try and reuse a socket that
166 # might have a POST body waiting to confuse us. We do
166 # might have a POST body waiting to confuse us. We do
167 # this by directly munging self.saved_headers because
167 # this by directly munging self.saved_headers because
168 # self._start_response ignores Connection headers.
168 # self._start_response ignores Connection headers.
169 self.saved_headers = [('Connection', 'Close')]
169 self.saved_headers = [('Connection', 'Close')]
170 self._write(b"Not Found")
170 self._write(b"Not Found")
171 self._done()
171 self._done()
172 return
172 return
173
173
174 env = {}
174 env = {}
175 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
175 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
176 env['REQUEST_METHOD'] = self.command
176 env['REQUEST_METHOD'] = self.command
177 env['SERVER_NAME'] = self.server.server_name
177 env['SERVER_NAME'] = self.server.server_name
178 env['SERVER_PORT'] = str(self.server.server_port)
178 env['SERVER_PORT'] = str(self.server.server_port)
179 env['REQUEST_URI'] = self.path
179 env['REQUEST_URI'] = self.path
180 env['SCRIPT_NAME'] = pycompat.sysstr(self.server.prefix)
180 env['SCRIPT_NAME'] = pycompat.sysstr(self.server.prefix)
181 env['PATH_INFO'] = pycompat.sysstr(path[len(self.server.prefix) :])
181 env['PATH_INFO'] = pycompat.sysstr(path[len(self.server.prefix) :])
182 env['REMOTE_HOST'] = self.client_address[0]
182 env['REMOTE_HOST'] = self.client_address[0]
183 env['REMOTE_ADDR'] = self.client_address[0]
183 env['REMOTE_ADDR'] = self.client_address[0]
184 env['QUERY_STRING'] = query or ''
184 env['QUERY_STRING'] = query or ''
185
185
186 if pycompat.ispy3:
186 if pycompat.ispy3:
187 if self.headers.get_content_type() is None:
187 if self.headers.get_content_type() is None:
188 env['CONTENT_TYPE'] = self.headers.get_default_type()
188 env['CONTENT_TYPE'] = self.headers.get_default_type()
189 else:
189 else:
190 env['CONTENT_TYPE'] = self.headers.get_content_type()
190 env['CONTENT_TYPE'] = self.headers.get_content_type()
191 length = self.headers.get('content-length')
191 length = self.headers.get('content-length')
192 else:
192 else:
193 if self.headers.typeheader is None:
193 if self.headers.typeheader is None:
194 env['CONTENT_TYPE'] = self.headers.type
194 env['CONTENT_TYPE'] = self.headers.type
195 else:
195 else:
196 env['CONTENT_TYPE'] = self.headers.typeheader
196 env['CONTENT_TYPE'] = self.headers.typeheader
197 length = self.headers.getheader('content-length')
197 length = self.headers.getheader('content-length')
198 if length:
198 if length:
199 env['CONTENT_LENGTH'] = length
199 env['CONTENT_LENGTH'] = length
200 for header in [
200 for header in [
201 h
201 h
202 for h in self.headers.keys()
202 for h in self.headers.keys()
203 if h.lower() not in ('content-type', 'content-length')
203 if h.lower() not in ('content-type', 'content-length')
204 ]:
204 ]:
205 hkey = 'HTTP_' + header.replace('-', '_').upper()
205 hkey = 'HTTP_' + header.replace('-', '_').upper()
206 hval = self.headers.get(header)
206 hval = self.headers.get(header)
207 hval = hval.replace('\n', '').strip()
207 hval = hval.replace('\n', '').strip()
208 if hval:
208 if hval:
209 env[hkey] = hval
209 env[hkey] = hval
210 env['SERVER_PROTOCOL'] = self.request_version
210 env['SERVER_PROTOCOL'] = self.request_version
211 env['wsgi.version'] = (1, 0)
211 env['wsgi.version'] = (1, 0)
212 env['wsgi.url_scheme'] = pycompat.sysstr(self.url_scheme)
212 env['wsgi.url_scheme'] = pycompat.sysstr(self.url_scheme)
213 if env.get('HTTP_EXPECT', b'').lower() == b'100-continue':
213 if env.get('HTTP_EXPECT', b'').lower() == b'100-continue':
214 self.rfile = common.continuereader(self.rfile, self.wfile.write)
214 self.rfile = common.continuereader(self.rfile, self.wfile.write)
215
215
216 env['wsgi.input'] = self.rfile
216 env['wsgi.input'] = self.rfile
217 env['wsgi.errors'] = _error_logger(self)
217 env['wsgi.errors'] = _error_logger(self)
218 env['wsgi.multithread'] = isinstance(
218 env['wsgi.multithread'] = isinstance(
219 self.server, socketserver.ThreadingMixIn
219 self.server, socketserver.ThreadingMixIn
220 )
220 )
221 if util.safehasattr(socketserver, b'ForkingMixIn'):
221 if util.safehasattr(socketserver, b'ForkingMixIn'):
222 env['wsgi.multiprocess'] = isinstance(
222 env['wsgi.multiprocess'] = isinstance(
223 self.server, socketserver.ForkingMixIn
223 self.server, socketserver.ForkingMixIn
224 )
224 )
225 else:
225 else:
226 env['wsgi.multiprocess'] = False
226 env['wsgi.multiprocess'] = False
227
227
228 env['wsgi.run_once'] = 0
228 env['wsgi.run_once'] = 0
229
229
230 wsgiref.validate.check_environ(env)
230 wsgiref.validate.check_environ(env)
231
231
232 self.saved_status = None
232 self.saved_status = None
233 self.saved_headers = []
233 self.saved_headers = []
234 self.length = None
234 self.length = None
235 self._chunked = None
235 self._chunked = None
236 for chunk in self.server.application(env, self._start_response):
236 for chunk in self.server.application(env, self._start_response):
237 self._write(chunk)
237 self._write(chunk)
238 if not self.sent_headers:
238 if not self.sent_headers:
239 self.send_headers()
239 self.send_headers()
240 self._done()
240 self._done()
241
241
242 def send_headers(self):
242 def send_headers(self):
243 if not self.saved_status:
243 if not self.saved_status:
244 raise AssertionError(
244 raise AssertionError(
245 b"Sending headers before start_response() called"
245 b"Sending headers before start_response() called"
246 )
246 )
247 saved_status = self.saved_status.split(None, 1)
247 saved_status = self.saved_status.split(None, 1)
248 saved_status[0] = int(saved_status[0])
248 saved_status[0] = int(saved_status[0])
249 self.send_response(*saved_status)
249 self.send_response(*saved_status)
250 self.length = None
250 self.length = None
251 self._chunked = False
251 self._chunked = False
252 for h in self.saved_headers:
252 for h in self.saved_headers:
253 self.send_header(*h)
253 self.send_header(*h)
254 if h[0].lower() == 'content-length':
254 if h[0].lower() == 'content-length':
255 self.length = int(h[1])
255 self.length = int(h[1])
256 if self.length is None and saved_status[0] != common.HTTP_NOT_MODIFIED:
256 if self.length is None and saved_status[0] != common.HTTP_NOT_MODIFIED:
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.modernssl
316 sslutil.modernssl
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.activeCount() # silence pyflakes and bypass demandimport
344 threading.activeCount() # 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(object):
351 class _mixin(object):
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 = socket.getfqdn(addr[0])
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 try:
412 try:
413 import mimetypes
413 import mimetypes
414
414
415 mimetypes.init()
415 mimetypes.init()
416 except UnicodeDecodeError:
416 except UnicodeDecodeError:
417 # Python 2.x's mimetypes module attempts to decode strings
417 # Python 2.x's mimetypes module attempts to decode strings
418 # from Windows' ANSI APIs as ascii (fail), then re-encode them
418 # from Windows' ANSI APIs as ascii (fail), then re-encode them
419 # as ascii (clown fail), because the default Python Unicode
419 # as ascii (clown fail), because the default Python Unicode
420 # codec is hardcoded as ascii.
420 # codec is hardcoded as ascii.
421
421
422 sys.argv # unwrap demand-loader so that reload() works
422 sys.argv # unwrap demand-loader so that reload() works
423 # resurrect sys.setdefaultencoding()
423 # resurrect sys.setdefaultencoding()
424 try:
424 try:
425 importlib.reload(sys)
425 importlib.reload(sys)
426 except AttributeError:
426 except AttributeError:
427 reload(sys)
427 reload(sys)
428 oldenc = sys.getdefaultencoding()
428 oldenc = sys.getdefaultencoding()
429 sys.setdefaultencoding(b"latin1") # or any full 8-bit encoding
429 sys.setdefaultencoding(b"latin1") # or any full 8-bit encoding
430 mimetypes.init()
430 mimetypes.init()
431 sys.setdefaultencoding(oldenc)
431 sys.setdefaultencoding(oldenc)
432
432
433 address = ui.config(b'web', b'address')
433 address = ui.config(b'web', b'address')
434 port = util.getport(ui.config(b'web', b'port'))
434 port = util.getport(ui.config(b'web', b'port'))
435 try:
435 try:
436 return cls(ui, app, (address, port), handler)
436 return cls(ui, app, (address, port), handler)
437 except socket.error as inst:
437 except socket.error as inst:
438 raise error.Abort(
438 raise error.Abort(
439 _(b"cannot start server at '%s:%d': %s")
439 _(b"cannot start server at '%s:%d': %s")
440 % (address, port, encoding.strtolocal(inst.args[1]))
440 % (address, port, encoding.strtolocal(inst.args[1]))
441 )
441 )
@@ -1,102 +1,109 b''
1 #require serve
1 #require serve
2
2
3 $ hgserve()
3 $ hgserve()
4 > {
4 > {
5 > hg serve -a localhost -d --pid-file=hg.pid -E errors.log -v $@ \
5 > hg serve -a localhost -d --pid-file=hg.pid -E errors.log -v $@ \
6 > | sed -e "s/:$HGPORT1\\([^0-9]\\)/:HGPORT1\1/g" \
6 > | sed -e "s/:$HGPORT1\\([^0-9]\\)/:HGPORT1\1/g" \
7 > -e "s/:$HGPORT2\\([^0-9]\\)/:HGPORT2\1/g" \
7 > -e "s/:$HGPORT2\\([^0-9]\\)/:HGPORT2\1/g" \
8 > -e 's/http:\/\/[^/]*\//http:\/\/localhost\//'
8 > -e 's/http:\/\/[^/]*\//http:\/\/localhost\//'
9 > if [ -f hg.pid ]; then
9 > if [ -f hg.pid ]; then
10 > killdaemons.py hg.pid
10 > killdaemons.py hg.pid
11 > fi
11 > fi
12 > echo % errors
12 > echo % errors
13 > cat errors.log
13 > cat errors.log
14 > }
14 > }
15
15
16 $ hg init test
16 $ hg init test
17 $ cd test
17 $ cd test
18 $ echo '[web]' > .hg/hgrc
18 $ echo '[web]' > .hg/hgrc
19 $ echo 'accesslog = access.log' >> .hg/hgrc
19 $ echo 'accesslog = access.log' >> .hg/hgrc
20 $ echo "port = $HGPORT1" >> .hg/hgrc
20 $ echo "port = $HGPORT1" >> .hg/hgrc
21
21
22 Without -v
22 Without -v
23
23
24 $ hg serve -a localhost -p $HGPORT -d --pid-file=hg.pid -E errors.log
24 $ hg serve -a localhost -p $HGPORT -d --pid-file=hg.pid -E errors.log
25 $ cat hg.pid >> "$DAEMON_PIDS"
25 $ cat hg.pid >> "$DAEMON_PIDS"
26 $ if [ -f access.log ]; then
26 $ if [ -f access.log ]; then
27 > echo 'access log created - .hg/hgrc respected'
27 > echo 'access log created - .hg/hgrc respected'
28 > fi
28 > fi
29 access log created - .hg/hgrc respected
29 access log created - .hg/hgrc respected
30
30
31 errors
31 errors
32
32
33 $ cat errors.log
33 $ cat errors.log
34
34
35 With -v
35 With -v
36
36
37 $ hgserve
37 $ hgserve
38 listening at http://localhost/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
38 listening at http://localhost/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
39 % errors
39 % errors
40
40
41 With -v and -p HGPORT2
41 With -v and -p HGPORT2
42
42
43 $ hgserve -p "$HGPORT2"
43 $ hgserve -p "$HGPORT2"
44 listening at http://localhost/ (bound to *$LOCALIP*:HGPORT2) (glob) (?)
44 listening at http://localhost/ (bound to *$LOCALIP*:HGPORT2) (glob) (?)
45 % errors
45 % errors
46
46
47 With -v and -p daytime (should fail because low port)
47 With -v and -p daytime (should fail because low port)
48
48
49 #if no-root no-windows
49 #if no-root no-windows
50 $ KILLQUIETLY=Y
50 $ KILLQUIETLY=Y
51 $ hgserve -p daytime
51 $ hgserve -p daytime
52 abort: cannot start server at 'localhost:13': Permission denied
52 abort: cannot start server at 'localhost:13': Permission denied
53 abort: child process failed to start
53 abort: child process failed to start
54 % errors
54 % errors
55 $ KILLQUIETLY=N
55 $ KILLQUIETLY=N
56 #endif
56 #endif
57
57
58 With --prefix foo
58 With --prefix foo
59
59
60 $ hgserve --prefix foo
60 $ hgserve --prefix foo
61 listening at http://localhost/foo/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
61 listening at http://localhost/foo/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
62 % errors
62 % errors
63
63
64 With --prefix /foo
64 With --prefix /foo
65
65
66 $ hgserve --prefix /foo
66 $ hgserve --prefix /foo
67 listening at http://localhost/foo/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
67 listening at http://localhost/foo/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
68 % errors
68 % errors
69
69
70 With --prefix foo/
70 With --prefix foo/
71
71
72 $ hgserve --prefix foo/
72 $ hgserve --prefix foo/
73 listening at http://localhost/foo/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
73 listening at http://localhost/foo/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
74 % errors
74 % errors
75
75
76 With --prefix /foo/
76 With --prefix /foo/
77
77
78 $ hgserve --prefix /foo/
78 $ hgserve --prefix /foo/
79 listening at http://localhost/foo/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
79 listening at http://localhost/foo/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
80 % errors
80 % errors
81
81
82 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
82 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
83
83
84 With out of bounds accesses
84 With out of bounds accesses
85
85
86 $ rm access.log
86 $ rm access.log
87 $ hg serve -a localhost -p $HGPORT -d --prefix some/dir \
87 $ hg serve -a localhost -p $HGPORT -d --prefix some/dir \
88 > --pid-file=hg.pid -E errors.log
88 > --pid-file=hg.pid -E errors.log
89 $ cat hg.pid >> "$DAEMON_PIDS"
89 $ cat hg.pid >> "$DAEMON_PIDS"
90
90
91 $ hg id http://localhost:$HGPORT/some/dir7
91 $ hg id http://localhost:$HGPORT/some/dir7
92 abort: HTTP Error 404: Not Found
92 abort: HTTP Error 404: Not Found
93 [255]
93 [255]
94 $ hg id http://localhost:$HGPORT/some
94 $ hg id http://localhost:$HGPORT/some
95 abort: HTTP Error 404: Not Found
95 abort: HTTP Error 404: Not Found
96 [255]
96 [255]
97
97
98 $ cat access.log errors.log
98 $ cat access.log errors.log
99 $LOCALIP - - [$LOGDATE$] "GET /some/dir7?cmd=capabilities HTTP/1.1" 404 - (glob)
99 $LOCALIP - - [$LOGDATE$] "GET /some/dir7?cmd=capabilities HTTP/1.1" 404 - (glob)
100 $LOCALIP - - [$LOGDATE$] "GET /some?cmd=capabilities HTTP/1.1" 404 - (glob)
100 $LOCALIP - - [$LOGDATE$] "GET /some?cmd=capabilities HTTP/1.1" 404 - (glob)
101
101
102 $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
103
104 issue6362: Previously, this crashed on Python 3
105
106 $ hg serve -a 0.0.0.0 -d
107 listening at http://*:$HGPORT1/ (bound to *:$HGPORT1) (glob)
108
102 $ cd ..
109 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now