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