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