##// END OF EJS Templates
serve: catch and log all Exceptions, not only StandardException...
Mads Kiilerich -
r13443:8fa83d71 stable
parent child Browse files
Show More
@@ -1,309 +1,309
1 1 # hgweb/server.py - The standalone hg web server.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
10 10 from mercurial import util, error
11 11 from mercurial.i18n import _
12 12
13 13 def _splitURI(uri):
14 14 """ Return path and query splited from uri
15 15
16 16 Just like CGI environment, the path is unquoted, the query is
17 17 not.
18 18 """
19 19 if '?' in uri:
20 20 path, query = uri.split('?', 1)
21 21 else:
22 22 path, query = uri, ''
23 23 return urllib.unquote(path), query
24 24
25 25 class _error_logger(object):
26 26 def __init__(self, handler):
27 27 self.handler = handler
28 28 def flush(self):
29 29 pass
30 30 def write(self, str):
31 31 self.writelines(str.split('\n'))
32 32 def writelines(self, seq):
33 33 for msg in seq:
34 34 self.handler.log_error("HG error: %s", msg)
35 35
36 36 class _httprequesthandler(BaseHTTPServer.BaseHTTPRequestHandler):
37 37
38 38 url_scheme = 'http'
39 39
40 40 @staticmethod
41 41 def preparehttpserver(httpserver, ssl_cert):
42 42 """Prepare .socket of new HTTPServer instance"""
43 43 pass
44 44
45 45 def __init__(self, *args, **kargs):
46 46 self.protocol_version = 'HTTP/1.1'
47 47 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
48 48
49 49 def _log_any(self, fp, format, *args):
50 50 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
51 51 self.log_date_time_string(),
52 52 format % args))
53 53 fp.flush()
54 54
55 55 def log_error(self, format, *args):
56 56 self._log_any(self.server.errorlog, format, *args)
57 57
58 58 def log_message(self, format, *args):
59 59 self._log_any(self.server.accesslog, format, *args)
60 60
61 61 def do_write(self):
62 62 try:
63 63 self.do_hgweb()
64 64 except socket.error, inst:
65 65 if inst[0] != errno.EPIPE:
66 66 raise
67 67
68 68 def do_POST(self):
69 69 try:
70 70 self.do_write()
71 except StandardError:
71 except Exception:
72 72 self._start_response("500 Internal Server Error", [])
73 73 self._write("Internal Server Error")
74 74 tb = "".join(traceback.format_exception(*sys.exc_info()))
75 75 self.log_error("Exception happened during processing "
76 76 "request '%s':\n%s", self.path, tb)
77 77
78 78 def do_GET(self):
79 79 self.do_POST()
80 80
81 81 def do_hgweb(self):
82 82 path, query = _splitURI(self.path)
83 83
84 84 env = {}
85 85 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
86 86 env['REQUEST_METHOD'] = self.command
87 87 env['SERVER_NAME'] = self.server.server_name
88 88 env['SERVER_PORT'] = str(self.server.server_port)
89 89 env['REQUEST_URI'] = self.path
90 90 env['SCRIPT_NAME'] = self.server.prefix
91 91 env['PATH_INFO'] = path[len(self.server.prefix):]
92 92 env['REMOTE_HOST'] = self.client_address[0]
93 93 env['REMOTE_ADDR'] = self.client_address[0]
94 94 if query:
95 95 env['QUERY_STRING'] = query
96 96
97 97 if self.headers.typeheader is None:
98 98 env['CONTENT_TYPE'] = self.headers.type
99 99 else:
100 100 env['CONTENT_TYPE'] = self.headers.typeheader
101 101 length = self.headers.getheader('content-length')
102 102 if length:
103 103 env['CONTENT_LENGTH'] = length
104 104 for header in [h for h in self.headers.keys()
105 105 if h not in ('content-type', 'content-length')]:
106 106 hkey = 'HTTP_' + header.replace('-', '_').upper()
107 107 hval = self.headers.getheader(header)
108 108 hval = hval.replace('\n', '').strip()
109 109 if hval:
110 110 env[hkey] = hval
111 111 env['SERVER_PROTOCOL'] = self.request_version
112 112 env['wsgi.version'] = (1, 0)
113 113 env['wsgi.url_scheme'] = self.url_scheme
114 114 env['wsgi.input'] = self.rfile
115 115 env['wsgi.errors'] = _error_logger(self)
116 116 env['wsgi.multithread'] = isinstance(self.server,
117 117 SocketServer.ThreadingMixIn)
118 118 env['wsgi.multiprocess'] = isinstance(self.server,
119 119 SocketServer.ForkingMixIn)
120 120 env['wsgi.run_once'] = 0
121 121
122 122 self.close_connection = True
123 123 self.saved_status = None
124 124 self.saved_headers = []
125 125 self.sent_headers = False
126 126 self.length = None
127 127 for chunk in self.server.application(env, self._start_response):
128 128 self._write(chunk)
129 129
130 130 def send_headers(self):
131 131 if not self.saved_status:
132 132 raise AssertionError("Sending headers before "
133 133 "start_response() called")
134 134 saved_status = self.saved_status.split(None, 1)
135 135 saved_status[0] = int(saved_status[0])
136 136 self.send_response(*saved_status)
137 137 should_close = True
138 138 for h in self.saved_headers:
139 139 self.send_header(*h)
140 140 if h[0].lower() == 'content-length':
141 141 should_close = False
142 142 self.length = int(h[1])
143 143 # The value of the Connection header is a list of case-insensitive
144 144 # tokens separated by commas and optional whitespace.
145 145 if 'close' in [token.strip().lower() for token in
146 146 self.headers.get('connection', '').split(',')]:
147 147 should_close = True
148 148 if should_close:
149 149 self.send_header('Connection', 'close')
150 150 self.close_connection = should_close
151 151 self.end_headers()
152 152 self.sent_headers = True
153 153
154 154 def _start_response(self, http_status, headers, exc_info=None):
155 155 code, msg = http_status.split(None, 1)
156 156 code = int(code)
157 157 self.saved_status = http_status
158 158 bad_headers = ('connection', 'transfer-encoding')
159 159 self.saved_headers = [h for h in headers
160 160 if h[0].lower() not in bad_headers]
161 161 return self._write
162 162
163 163 def _write(self, data):
164 164 if not self.saved_status:
165 165 raise AssertionError("data written before start_response() called")
166 166 elif not self.sent_headers:
167 167 self.send_headers()
168 168 if self.length is not None:
169 169 if len(data) > self.length:
170 170 raise AssertionError("Content-length header sent, but more "
171 171 "bytes than specified are being written.")
172 172 self.length = self.length - len(data)
173 173 self.wfile.write(data)
174 174 self.wfile.flush()
175 175
176 176 class _httprequesthandleropenssl(_httprequesthandler):
177 177 """HTTPS handler based on pyOpenSSL"""
178 178
179 179 url_scheme = 'https'
180 180
181 181 @staticmethod
182 182 def preparehttpserver(httpserver, ssl_cert):
183 183 try:
184 184 import OpenSSL
185 185 OpenSSL.SSL.Context
186 186 except ImportError:
187 187 raise util.Abort(_("SSL support is unavailable"))
188 188 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
189 189 ctx.use_privatekey_file(ssl_cert)
190 190 ctx.use_certificate_file(ssl_cert)
191 191 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
192 192 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
193 193 httpserver.server_bind()
194 194 httpserver.server_activate()
195 195
196 196 def setup(self):
197 197 self.connection = self.request
198 198 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
199 199 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
200 200
201 201 def do_write(self):
202 202 import OpenSSL
203 203 try:
204 204 _httprequesthandler.do_write(self)
205 205 except OpenSSL.SSL.SysCallError, inst:
206 206 if inst.args[0] != errno.EPIPE:
207 207 raise
208 208
209 209 def handle_one_request(self):
210 210 import OpenSSL
211 211 try:
212 212 _httprequesthandler.handle_one_request(self)
213 213 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
214 214 self.close_connection = True
215 215 pass
216 216
217 217 class _httprequesthandlerssl(_httprequesthandler):
218 218 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
219 219
220 220 url_scheme = 'https'
221 221
222 222 @staticmethod
223 223 def preparehttpserver(httpserver, ssl_cert):
224 224 try:
225 225 import ssl
226 226 ssl.wrap_socket
227 227 except ImportError:
228 228 raise util.Abort(_("SSL support is unavailable"))
229 229 httpserver.socket = ssl.wrap_socket(httpserver.socket, server_side=True,
230 230 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_SSLv23)
231 231
232 232 def setup(self):
233 233 self.connection = self.request
234 234 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
235 235 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
236 236
237 237 try:
238 238 from threading import activeCount
239 239 _mixin = SocketServer.ThreadingMixIn
240 240 except ImportError:
241 241 if hasattr(os, "fork"):
242 242 _mixin = SocketServer.ForkingMixIn
243 243 else:
244 244 class _mixin:
245 245 pass
246 246
247 247 def openlog(opt, default):
248 248 if opt and opt != '-':
249 249 return open(opt, 'a')
250 250 return default
251 251
252 252 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
253 253
254 254 # SO_REUSEADDR has broken semantics on windows
255 255 if os.name == 'nt':
256 256 allow_reuse_address = 0
257 257
258 258 def __init__(self, ui, app, addr, handler, **kwargs):
259 259 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
260 260 self.daemon_threads = True
261 261 self.application = app
262 262
263 263 handler.preparehttpserver(self, ui.config('web', 'certificate'))
264 264
265 265 prefix = ui.config('web', 'prefix', '')
266 266 if prefix:
267 267 prefix = '/' + prefix.strip('/')
268 268 self.prefix = prefix
269 269
270 270 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
271 271 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
272 272 self.accesslog = alog
273 273 self.errorlog = elog
274 274
275 275 self.addr, self.port = self.socket.getsockname()[0:2]
276 276 self.fqaddr = socket.getfqdn(addr[0])
277 277
278 278 class IPv6HTTPServer(MercurialHTTPServer):
279 279 address_family = getattr(socket, 'AF_INET6', None)
280 280 def __init__(self, *args, **kwargs):
281 281 if self.address_family is None:
282 282 raise error.RepoError(_('IPv6 is not available on this system'))
283 283 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
284 284
285 285 def create_server(ui, app):
286 286
287 287 if ui.config('web', 'certificate'):
288 288 if sys.version_info >= (2, 6):
289 289 handler = _httprequesthandlerssl
290 290 else:
291 291 handler = _httprequesthandleropenssl
292 292 else:
293 293 handler = _httprequesthandler
294 294
295 295 if ui.configbool('web', 'ipv6'):
296 296 cls = IPv6HTTPServer
297 297 else:
298 298 cls = MercurialHTTPServer
299 299
300 300 # ugly hack due to python issue5853 (for threaded use)
301 301 import mimetypes; mimetypes.init()
302 302
303 303 address = ui.config('web', 'address', '')
304 304 port = util.getport(ui.config('web', 'port', 8000))
305 305 try:
306 306 return cls(ui, app, (address, port), handler)
307 307 except socket.error, inst:
308 308 raise util.Abort(_("cannot start server at '%s:%d': %s")
309 309 % (address, port, inst.args[1]))
General Comments 0
You need to be logged in to leave comments. Login now