##// END OF EJS Templates
Handle exceptions in do_hgweb: Send "Internal Server Error", log traceback
Thomas Arendsen Hein -
r4015:769be3c5 default
parent child Browse files
Show More
@@ -1,239 +1,246 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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from mercurial.demandload import demandload
9 from mercurial.demandload import demandload
10 import os, sys, errno
10 import os, sys, errno
11 demandload(globals(), "urllib BaseHTTPServer socket SocketServer")
11 demandload(globals(), "urllib BaseHTTPServer socket SocketServer traceback")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
13 demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication")
13 demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication")
14 from mercurial.i18n import gettext as _
14 from mercurial.i18n import gettext as _
15
15
16 def _splitURI(uri):
16 def _splitURI(uri):
17 """ Return path and query splited from uri
17 """ Return path and query splited from uri
18
18
19 Just like CGI environment, the path is unquoted, the query is
19 Just like CGI environment, the path is unquoted, the query is
20 not.
20 not.
21 """
21 """
22 if '?' in uri:
22 if '?' in uri:
23 path, query = uri.split('?', 1)
23 path, query = uri.split('?', 1)
24 else:
24 else:
25 path, query = uri, ''
25 path, query = uri, ''
26 return urllib.unquote(path), query
26 return urllib.unquote(path), query
27
27
28 class _error_logger(object):
28 class _error_logger(object):
29 def __init__(self, handler):
29 def __init__(self, handler):
30 self.handler = handler
30 self.handler = handler
31 def flush(self):
31 def flush(self):
32 pass
32 pass
33 def write(self, str):
33 def write(self, str):
34 self.writelines(str.split('\n'))
34 self.writelines(str.split('\n'))
35 def writelines(self, seq):
35 def writelines(self, seq):
36 for msg in seq:
36 for msg in seq:
37 self.handler.log_error("HG error: %s", msg)
37 self.handler.log_error("HG error: %s", msg)
38
38
39 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
39 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
40 def __init__(self, *args, **kargs):
40 def __init__(self, *args, **kargs):
41 self.protocol_version = 'HTTP/1.1'
41 self.protocol_version = 'HTTP/1.1'
42 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
42 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
43
43
44 def log_error(self, format, *args):
44 def log_error(self, format, *args):
45 errorlog = self.server.errorlog
45 errorlog = self.server.errorlog
46 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
46 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
47 self.log_date_time_string(),
47 self.log_date_time_string(),
48 format % args))
48 format % args))
49
49
50 def log_message(self, format, *args):
50 def log_message(self, format, *args):
51 accesslog = self.server.accesslog
51 accesslog = self.server.accesslog
52 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
52 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
53 self.log_date_time_string(),
53 self.log_date_time_string(),
54 format % args))
54 format % args))
55
55
56 def do_POST(self):
56 def do_POST(self):
57 try:
57 try:
58 self.do_hgweb()
58 try:
59 except socket.error, inst:
59 self.do_hgweb()
60 if inst[0] != errno.EPIPE:
60 except socket.error, inst:
61 raise
61 if inst[0] != errno.EPIPE:
62 raise
63 except StandardError, inst:
64 self._start_response("500 Internal Server Error", [])
65 self._write("Internal Server Error")
66 tb = "".join(traceback.format_exception(*sys.exc_info()))
67 self.log_error("Exception happened during processing request '%s':\n%s",
68 self.path, tb)
62
69
63 def do_GET(self):
70 def do_GET(self):
64 self.do_POST()
71 self.do_POST()
65
72
66 def do_hgweb(self):
73 def do_hgweb(self):
67 path_info, query = _splitURI(self.path)
74 path_info, query = _splitURI(self.path)
68
75
69 env = {}
76 env = {}
70 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
77 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
71 env['REQUEST_METHOD'] = self.command
78 env['REQUEST_METHOD'] = self.command
72 env['SERVER_NAME'] = self.server.server_name
79 env['SERVER_NAME'] = self.server.server_name
73 env['SERVER_PORT'] = str(self.server.server_port)
80 env['SERVER_PORT'] = str(self.server.server_port)
74 env['REQUEST_URI'] = self.path
81 env['REQUEST_URI'] = self.path
75 env['PATH_INFO'] = path_info
82 env['PATH_INFO'] = path_info
76 if query:
83 if query:
77 env['QUERY_STRING'] = query
84 env['QUERY_STRING'] = query
78 host = self.address_string()
85 host = self.address_string()
79 if host != self.client_address[0]:
86 if host != self.client_address[0]:
80 env['REMOTE_HOST'] = host
87 env['REMOTE_HOST'] = host
81 env['REMOTE_ADDR'] = self.client_address[0]
88 env['REMOTE_ADDR'] = self.client_address[0]
82
89
83 if self.headers.typeheader is None:
90 if self.headers.typeheader is None:
84 env['CONTENT_TYPE'] = self.headers.type
91 env['CONTENT_TYPE'] = self.headers.type
85 else:
92 else:
86 env['CONTENT_TYPE'] = self.headers.typeheader
93 env['CONTENT_TYPE'] = self.headers.typeheader
87 length = self.headers.getheader('content-length')
94 length = self.headers.getheader('content-length')
88 if length:
95 if length:
89 env['CONTENT_LENGTH'] = length
96 env['CONTENT_LENGTH'] = length
90 for header in [h for h in self.headers.keys() \
97 for header in [h for h in self.headers.keys() \
91 if h not in ('content-type', 'content-length')]:
98 if h not in ('content-type', 'content-length')]:
92 hkey = 'HTTP_' + header.replace('-', '_').upper()
99 hkey = 'HTTP_' + header.replace('-', '_').upper()
93 hval = self.headers.getheader(header)
100 hval = self.headers.getheader(header)
94 hval = hval.replace('\n', '').strip()
101 hval = hval.replace('\n', '').strip()
95 if hval:
102 if hval:
96 env[hkey] = hval
103 env[hkey] = hval
97 env['SERVER_PROTOCOL'] = self.request_version
104 env['SERVER_PROTOCOL'] = self.request_version
98 env['wsgi.version'] = (1, 0)
105 env['wsgi.version'] = (1, 0)
99 env['wsgi.url_scheme'] = 'http'
106 env['wsgi.url_scheme'] = 'http'
100 env['wsgi.input'] = self.rfile
107 env['wsgi.input'] = self.rfile
101 env['wsgi.errors'] = _error_logger(self)
108 env['wsgi.errors'] = _error_logger(self)
102 env['wsgi.multithread'] = isinstance(self.server,
109 env['wsgi.multithread'] = isinstance(self.server,
103 SocketServer.ThreadingMixIn)
110 SocketServer.ThreadingMixIn)
104 env['wsgi.multiprocess'] = isinstance(self.server,
111 env['wsgi.multiprocess'] = isinstance(self.server,
105 SocketServer.ForkingMixIn)
112 SocketServer.ForkingMixIn)
106 env['wsgi.run_once'] = 0
113 env['wsgi.run_once'] = 0
107
114
108 self.close_connection = True
115 self.close_connection = True
109 self.saved_status = None
116 self.saved_status = None
110 self.saved_headers = []
117 self.saved_headers = []
111 self.sent_headers = False
118 self.sent_headers = False
112 self.length = None
119 self.length = None
113 req = self.server.reqmaker(env, self._start_response)
120 req = self.server.reqmaker(env, self._start_response)
114 for data in req:
121 for data in req:
115 if data:
122 if data:
116 self._write(data)
123 self._write(data)
117
124
118 def send_headers(self):
125 def send_headers(self):
119 if not self.saved_status:
126 if not self.saved_status:
120 raise AssertionError("Sending headers before start_response() called")
127 raise AssertionError("Sending headers before start_response() called")
121 saved_status = self.saved_status.split(None, 1)
128 saved_status = self.saved_status.split(None, 1)
122 saved_status[0] = int(saved_status[0])
129 saved_status[0] = int(saved_status[0])
123 self.send_response(*saved_status)
130 self.send_response(*saved_status)
124 should_close = True
131 should_close = True
125 for h in self.saved_headers:
132 for h in self.saved_headers:
126 self.send_header(*h)
133 self.send_header(*h)
127 if h[0].lower() == 'content-length':
134 if h[0].lower() == 'content-length':
128 should_close = False
135 should_close = False
129 self.length = int(h[1])
136 self.length = int(h[1])
130 # The value of the Connection header is a list of case-insensitive
137 # The value of the Connection header is a list of case-insensitive
131 # tokens separated by commas and optional whitespace.
138 # tokens separated by commas and optional whitespace.
132 if 'close' in [token.strip().lower() for token in
139 if 'close' in [token.strip().lower() for token in
133 self.headers.get('connection', '').split(',')]:
140 self.headers.get('connection', '').split(',')]:
134 should_close = True
141 should_close = True
135 if should_close:
142 if should_close:
136 self.send_header('Connection', 'close')
143 self.send_header('Connection', 'close')
137 self.close_connection = should_close
144 self.close_connection = should_close
138 self.end_headers()
145 self.end_headers()
139 self.sent_headers = True
146 self.sent_headers = True
140
147
141 def _start_response(self, http_status, headers, exc_info=None):
148 def _start_response(self, http_status, headers, exc_info=None):
142 code, msg = http_status.split(None, 1)
149 code, msg = http_status.split(None, 1)
143 code = int(code)
150 code = int(code)
144 self.saved_status = http_status
151 self.saved_status = http_status
145 bad_headers = ('connection', 'transfer-encoding')
152 bad_headers = ('connection', 'transfer-encoding')
146 self.saved_headers = [ h for h in headers \
153 self.saved_headers = [ h for h in headers \
147 if h[0].lower() not in bad_headers ]
154 if h[0].lower() not in bad_headers ]
148 return self._write
155 return self._write
149
156
150 def _write(self, data):
157 def _write(self, data):
151 if not self.saved_status:
158 if not self.saved_status:
152 raise AssertionError("data written before start_response() called")
159 raise AssertionError("data written before start_response() called")
153 elif not self.sent_headers:
160 elif not self.sent_headers:
154 self.send_headers()
161 self.send_headers()
155 if self.length is not None:
162 if self.length is not None:
156 if len(data) > self.length:
163 if len(data) > self.length:
157 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
164 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
158 self.length = self.length - len(data)
165 self.length = self.length - len(data)
159 self.wfile.write(data)
166 self.wfile.write(data)
160 self.wfile.flush()
167 self.wfile.flush()
161
168
162 def create_server(ui, repo):
169 def create_server(ui, repo):
163 use_threads = True
170 use_threads = True
164
171
165 def openlog(opt, default):
172 def openlog(opt, default):
166 if opt and opt != '-':
173 if opt and opt != '-':
167 return open(opt, 'w')
174 return open(opt, 'w')
168 return default
175 return default
169
176
170 address = ui.config("web", "address", "")
177 address = ui.config("web", "address", "")
171 port = int(ui.config("web", "port", 8000))
178 port = int(ui.config("web", "port", 8000))
172 use_ipv6 = ui.configbool("web", "ipv6")
179 use_ipv6 = ui.configbool("web", "ipv6")
173 webdir_conf = ui.config("web", "webdir_conf")
180 webdir_conf = ui.config("web", "webdir_conf")
174 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
181 accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
175 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
182 errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
176
183
177 if use_threads:
184 if use_threads:
178 try:
185 try:
179 from threading import activeCount
186 from threading import activeCount
180 except ImportError:
187 except ImportError:
181 use_threads = False
188 use_threads = False
182
189
183 if use_threads:
190 if use_threads:
184 _mixin = SocketServer.ThreadingMixIn
191 _mixin = SocketServer.ThreadingMixIn
185 else:
192 else:
186 if hasattr(os, "fork"):
193 if hasattr(os, "fork"):
187 _mixin = SocketServer.ForkingMixIn
194 _mixin = SocketServer.ForkingMixIn
188 else:
195 else:
189 class _mixin:
196 class _mixin:
190 pass
197 pass
191
198
192 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
199 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
193 def __init__(self, *args, **kargs):
200 def __init__(self, *args, **kargs):
194 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
201 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
195 self.accesslog = accesslog
202 self.accesslog = accesslog
196 self.errorlog = errorlog
203 self.errorlog = errorlog
197 self.repo = repo
204 self.repo = repo
198 self.webdir_conf = webdir_conf
205 self.webdir_conf = webdir_conf
199 self.webdirmaker = hgwebdir
206 self.webdirmaker = hgwebdir
200 self.repoviewmaker = hgweb
207 self.repoviewmaker = hgweb
201 self.reqmaker = wsgiapplication(self.make_handler)
208 self.reqmaker = wsgiapplication(self.make_handler)
202 self.daemon_threads = True
209 self.daemon_threads = True
203
210
204 addr, port = self.socket.getsockname()[:2]
211 addr, port = self.socket.getsockname()[:2]
205 if addr in ('0.0.0.0', '::'):
212 if addr in ('0.0.0.0', '::'):
206 addr = socket.gethostname()
213 addr = socket.gethostname()
207 else:
214 else:
208 try:
215 try:
209 addr = socket.gethostbyaddr(addr)[0]
216 addr = socket.gethostbyaddr(addr)[0]
210 except socket.error:
217 except socket.error:
211 pass
218 pass
212 self.addr, self.port = addr, port
219 self.addr, self.port = addr, port
213
220
214 def make_handler(self):
221 def make_handler(self):
215 if self.webdir_conf:
222 if self.webdir_conf:
216 hgwebobj = self.webdirmaker(self.webdir_conf)
223 hgwebobj = self.webdirmaker(self.webdir_conf)
217 elif self.repo is not None:
224 elif self.repo is not None:
218 hgwebobj = self.repoviewmaker(repo.__class__(repo.ui,
225 hgwebobj = self.repoviewmaker(repo.__class__(repo.ui,
219 repo.origroot))
226 repo.origroot))
220 else:
227 else:
221 raise hg.RepoError(_("There is no Mercurial repository here"
228 raise hg.RepoError(_("There is no Mercurial repository here"
222 " (.hg not found)"))
229 " (.hg not found)"))
223 return hgwebobj
230 return hgwebobj
224
231
225 class IPv6HTTPServer(MercurialHTTPServer):
232 class IPv6HTTPServer(MercurialHTTPServer):
226 address_family = getattr(socket, 'AF_INET6', None)
233 address_family = getattr(socket, 'AF_INET6', None)
227
234
228 def __init__(self, *args, **kwargs):
235 def __init__(self, *args, **kwargs):
229 if self.address_family is None:
236 if self.address_family is None:
230 raise hg.RepoError(_('IPv6 not available on this system'))
237 raise hg.RepoError(_('IPv6 not available on this system'))
231 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
238 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
232
239
233 try:
240 try:
234 if use_ipv6:
241 if use_ipv6:
235 return IPv6HTTPServer((address, port), _hgwebhandler)
242 return IPv6HTTPServer((address, port), _hgwebhandler)
236 else:
243 else:
237 return MercurialHTTPServer((address, port), _hgwebhandler)
244 return MercurialHTTPServer((address, port), _hgwebhandler)
238 except socket.error, inst:
245 except socket.error, inst:
239 raise util.Abort(_('cannot start server: %s') % inst.args[1])
246 raise util.Abort(_('cannot start server: %s') % inst.args[1])
General Comments 0
You need to be logged in to leave comments. Login now