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