##// END OF EJS Templates
hgweb: send proper HTTP response after uncaught exception...
Gregory Szorc -
r23409:dc4d2cd3 stable
parent child Browse files
Show More
@@ -0,0 +1,17 b''
1 # A dummy extension that installs an hgweb command that throws an Exception.
2
3 from mercurial.hgweb import webcommands
4
5 def raiseerror(web, req, tmpl):
6 '''Dummy web command that raises an uncaught Exception.'''
7
8 # Simulate an error after partial response.
9 if 'partialresponse' in req.form:
10 req.respond(200, 'text/plain')
11 req.write('partial content\n')
12
13 raise AttributeError('I am an uncaught error!')
14
15 def extsetup(ui):
16 setattr(webcommands, 'raiseerror', raiseerror)
17 webcommands.__all__.append('raiseerror')
@@ -1,348 +1,349 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-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.hgweb import common
12 12 from mercurial.i18n import _
13 13
14 14 def _splitURI(uri):
15 15 """Return path and query that has been split from uri
16 16
17 17 Just like CGI environment, the path is unquoted, the query is
18 18 not.
19 19 """
20 20 if '?' in uri:
21 21 path, query = uri.split('?', 1)
22 22 else:
23 23 path, query = uri, ''
24 24 return urllib.unquote(path), query
25 25
26 26 class _error_logger(object):
27 27 def __init__(self, handler):
28 28 self.handler = handler
29 29 def flush(self):
30 30 pass
31 31 def write(self, str):
32 32 self.writelines(str.split('\n'))
33 33 def writelines(self, seq):
34 34 for msg in seq:
35 35 self.handler.log_error("HG error: %s", msg)
36 36
37 37 class _httprequesthandler(BaseHTTPServer.BaseHTTPRequestHandler):
38 38
39 39 url_scheme = 'http'
40 40
41 41 @staticmethod
42 42 def preparehttpserver(httpserver, ssl_cert):
43 43 """Prepare .socket of new HTTPServer instance"""
44 44 pass
45 45
46 46 def __init__(self, *args, **kargs):
47 47 self.protocol_version = 'HTTP/1.1'
48 48 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
49 49
50 50 def _log_any(self, fp, format, *args):
51 51 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
52 52 self.log_date_time_string(),
53 53 format % args))
54 54 fp.flush()
55 55
56 56 def log_error(self, format, *args):
57 57 self._log_any(self.server.errorlog, format, *args)
58 58
59 59 def log_message(self, format, *args):
60 60 self._log_any(self.server.accesslog, format, *args)
61 61
62 62 def log_request(self, code='-', size='-'):
63 63 xheaders = []
64 64 if util.safehasattr(self, 'headers'):
65 65 xheaders = [h for h in self.headers.items()
66 66 if h[0].startswith('x-')]
67 67 self.log_message('"%s" %s %s%s',
68 68 self.requestline, str(code), str(size),
69 69 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
70 70
71 71 def do_write(self):
72 72 try:
73 73 self.do_hgweb()
74 74 except socket.error, inst:
75 75 if inst[0] != errno.EPIPE:
76 76 raise
77 77
78 78 def do_POST(self):
79 79 try:
80 80 self.do_write()
81 81 except Exception:
82 82 self._start_response("500 Internal Server Error", [])
83 83 self._write("Internal Server Error")
84 self._done()
84 85 tb = "".join(traceback.format_exception(*sys.exc_info()))
85 86 self.log_error("Exception happened during processing "
86 87 "request '%s':\n%s", self.path, tb)
87 88
88 89 def do_GET(self):
89 90 self.do_POST()
90 91
91 92 def do_hgweb(self):
92 93 path, query = _splitURI(self.path)
93 94
94 95 env = {}
95 96 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
96 97 env['REQUEST_METHOD'] = self.command
97 98 env['SERVER_NAME'] = self.server.server_name
98 99 env['SERVER_PORT'] = str(self.server.server_port)
99 100 env['REQUEST_URI'] = self.path
100 101 env['SCRIPT_NAME'] = self.server.prefix
101 102 env['PATH_INFO'] = path[len(self.server.prefix):]
102 103 env['REMOTE_HOST'] = self.client_address[0]
103 104 env['REMOTE_ADDR'] = self.client_address[0]
104 105 if query:
105 106 env['QUERY_STRING'] = query
106 107
107 108 if self.headers.typeheader is None:
108 109 env['CONTENT_TYPE'] = self.headers.type
109 110 else:
110 111 env['CONTENT_TYPE'] = self.headers.typeheader
111 112 length = self.headers.getheader('content-length')
112 113 if length:
113 114 env['CONTENT_LENGTH'] = length
114 115 for header in [h for h in self.headers.keys()
115 116 if h not in ('content-type', 'content-length')]:
116 117 hkey = 'HTTP_' + header.replace('-', '_').upper()
117 118 hval = self.headers.getheader(header)
118 119 hval = hval.replace('\n', '').strip()
119 120 if hval:
120 121 env[hkey] = hval
121 122 env['SERVER_PROTOCOL'] = self.request_version
122 123 env['wsgi.version'] = (1, 0)
123 124 env['wsgi.url_scheme'] = self.url_scheme
124 125 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
125 126 self.rfile = common.continuereader(self.rfile, self.wfile.write)
126 127
127 128 env['wsgi.input'] = self.rfile
128 129 env['wsgi.errors'] = _error_logger(self)
129 130 env['wsgi.multithread'] = isinstance(self.server,
130 131 SocketServer.ThreadingMixIn)
131 132 env['wsgi.multiprocess'] = isinstance(self.server,
132 133 SocketServer.ForkingMixIn)
133 134 env['wsgi.run_once'] = 0
134 135
135 136 self.saved_status = None
136 137 self.saved_headers = []
137 138 self.sent_headers = False
138 139 self.length = None
139 140 self._chunked = None
140 141 for chunk in self.server.application(env, self._start_response):
141 142 self._write(chunk)
142 143 if not self.sent_headers:
143 144 self.send_headers()
144 145 self._done()
145 146
146 147 def send_headers(self):
147 148 if not self.saved_status:
148 149 raise AssertionError("Sending headers before "
149 150 "start_response() called")
150 151 saved_status = self.saved_status.split(None, 1)
151 152 saved_status[0] = int(saved_status[0])
152 153 self.send_response(*saved_status)
153 154 self.length = None
154 155 self._chunked = False
155 156 for h in self.saved_headers:
156 157 self.send_header(*h)
157 158 if h[0].lower() == 'content-length':
158 159 self.length = int(h[1])
159 160 if (self.length is None and
160 161 saved_status[0] != common.HTTP_NOT_MODIFIED):
161 162 self._chunked = (not self.close_connection and
162 163 self.request_version == "HTTP/1.1")
163 164 if self._chunked:
164 165 self.send_header('Transfer-Encoding', 'chunked')
165 166 else:
166 167 self.send_header('Connection', 'close')
167 168 self.end_headers()
168 169 self.sent_headers = True
169 170
170 171 def _start_response(self, http_status, headers, exc_info=None):
171 172 code, msg = http_status.split(None, 1)
172 173 code = int(code)
173 174 self.saved_status = http_status
174 175 bad_headers = ('connection', 'transfer-encoding')
175 176 self.saved_headers = [h for h in headers
176 177 if h[0].lower() not in bad_headers]
177 178 return self._write
178 179
179 180 def _write(self, data):
180 181 if not self.saved_status:
181 182 raise AssertionError("data written before start_response() called")
182 183 elif not self.sent_headers:
183 184 self.send_headers()
184 185 if self.length is not None:
185 186 if len(data) > self.length:
186 187 raise AssertionError("Content-length header sent, but more "
187 188 "bytes than specified are being written.")
188 189 self.length = self.length - len(data)
189 190 elif self._chunked and data:
190 191 data = '%x\r\n%s\r\n' % (len(data), data)
191 192 self.wfile.write(data)
192 193 self.wfile.flush()
193 194
194 195 def _done(self):
195 196 if self._chunked:
196 197 self.wfile.write('0\r\n\r\n')
197 198 self.wfile.flush()
198 199
199 200 class _httprequesthandleropenssl(_httprequesthandler):
200 201 """HTTPS handler based on pyOpenSSL"""
201 202
202 203 url_scheme = 'https'
203 204
204 205 @staticmethod
205 206 def preparehttpserver(httpserver, ssl_cert):
206 207 try:
207 208 import OpenSSL
208 209 OpenSSL.SSL.Context
209 210 except ImportError:
210 211 raise util.Abort(_("SSL support is unavailable"))
211 212 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
212 213 ctx.use_privatekey_file(ssl_cert)
213 214 ctx.use_certificate_file(ssl_cert)
214 215 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
215 216 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
216 217 httpserver.server_bind()
217 218 httpserver.server_activate()
218 219
219 220 def setup(self):
220 221 self.connection = self.request
221 222 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
222 223 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
223 224
224 225 def do_write(self):
225 226 import OpenSSL
226 227 try:
227 228 _httprequesthandler.do_write(self)
228 229 except OpenSSL.SSL.SysCallError, inst:
229 230 if inst.args[0] != errno.EPIPE:
230 231 raise
231 232
232 233 def handle_one_request(self):
233 234 import OpenSSL
234 235 try:
235 236 _httprequesthandler.handle_one_request(self)
236 237 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
237 238 self.close_connection = True
238 239 pass
239 240
240 241 class _httprequesthandlerssl(_httprequesthandler):
241 242 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
242 243
243 244 url_scheme = 'https'
244 245
245 246 @staticmethod
246 247 def preparehttpserver(httpserver, ssl_cert):
247 248 try:
248 249 import ssl
249 250 ssl.wrap_socket
250 251 except ImportError:
251 252 raise util.Abort(_("SSL support is unavailable"))
252 253 httpserver.socket = ssl.wrap_socket(
253 254 httpserver.socket, server_side=True,
254 255 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_TLSv1)
255 256
256 257 def setup(self):
257 258 self.connection = self.request
258 259 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
259 260 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
260 261
261 262 try:
262 263 from threading import activeCount
263 264 activeCount() # silence pyflakes
264 265 _mixin = SocketServer.ThreadingMixIn
265 266 except ImportError:
266 267 if util.safehasattr(os, "fork"):
267 268 _mixin = SocketServer.ForkingMixIn
268 269 else:
269 270 class _mixin(object):
270 271 pass
271 272
272 273 def openlog(opt, default):
273 274 if opt and opt != '-':
274 275 return open(opt, 'a')
275 276 return default
276 277
277 278 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
278 279
279 280 # SO_REUSEADDR has broken semantics on windows
280 281 if os.name == 'nt':
281 282 allow_reuse_address = 0
282 283
283 284 def __init__(self, ui, app, addr, handler, **kwargs):
284 285 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
285 286 self.daemon_threads = True
286 287 self.application = app
287 288
288 289 handler.preparehttpserver(self, ui.config('web', 'certificate'))
289 290
290 291 prefix = ui.config('web', 'prefix', '')
291 292 if prefix:
292 293 prefix = '/' + prefix.strip('/')
293 294 self.prefix = prefix
294 295
295 296 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
296 297 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
297 298 self.accesslog = alog
298 299 self.errorlog = elog
299 300
300 301 self.addr, self.port = self.socket.getsockname()[0:2]
301 302 self.fqaddr = socket.getfqdn(addr[0])
302 303
303 304 class IPv6HTTPServer(MercurialHTTPServer):
304 305 address_family = getattr(socket, 'AF_INET6', None)
305 306 def __init__(self, *args, **kwargs):
306 307 if self.address_family is None:
307 308 raise error.RepoError(_('IPv6 is not available on this system'))
308 309 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
309 310
310 311 def create_server(ui, app):
311 312
312 313 if ui.config('web', 'certificate'):
313 314 if sys.version_info >= (2, 6):
314 315 handler = _httprequesthandlerssl
315 316 else:
316 317 handler = _httprequesthandleropenssl
317 318 else:
318 319 handler = _httprequesthandler
319 320
320 321 if ui.configbool('web', 'ipv6'):
321 322 cls = IPv6HTTPServer
322 323 else:
323 324 cls = MercurialHTTPServer
324 325
325 326 # ugly hack due to python issue5853 (for threaded use)
326 327 try:
327 328 import mimetypes
328 329 mimetypes.init()
329 330 except UnicodeDecodeError:
330 331 # Python 2.x's mimetypes module attempts to decode strings
331 332 # from Windows' ANSI APIs as ascii (fail), then re-encode them
332 333 # as ascii (clown fail), because the default Python Unicode
333 334 # codec is hardcoded as ascii.
334 335
335 336 sys.argv # unwrap demand-loader so that reload() works
336 337 reload(sys) # resurrect sys.setdefaultencoding()
337 338 oldenc = sys.getdefaultencoding()
338 339 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
339 340 mimetypes.init()
340 341 sys.setdefaultencoding(oldenc)
341 342
342 343 address = ui.config('web', 'address', '')
343 344 port = util.getport(ui.config('web', 'port', 8000))
344 345 try:
345 346 return cls(ui, app, (address, port), handler)
346 347 except socket.error, inst:
347 348 raise util.Abort(_("cannot start server at '%s:%d': %s")
348 349 % (address, port, inst.args[1]))
@@ -1,61 +1,60 b''
1 1 #!/usr/bin/env python
2 2
3 3 """This does HTTP GET requests given a host:port and path and returns
4 4 a subset of the headers plus the body of the result."""
5 5
6 6 import httplib, sys
7 7
8 8 try:
9 9 import msvcrt, os
10 10 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
11 11 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
12 12 except ImportError:
13 13 pass
14 14
15 15 twice = False
16 16 if '--twice' in sys.argv:
17 17 sys.argv.remove('--twice')
18 18 twice = True
19 19 headeronly = False
20 20 if '--headeronly' in sys.argv:
21 21 sys.argv.remove('--headeronly')
22 22 headeronly = True
23 23
24 24 reasons = {'Not modified': 'Not Modified'} # python 2.4
25 25
26 26 tag = None
27 27 def request(host, path, show):
28 28 assert not path.startswith('/'), path
29 29 global tag
30 30 headers = {}
31 31 if tag:
32 32 headers['If-None-Match'] = tag
33 33
34 34 conn = httplib.HTTPConnection(host)
35 35 conn.request("GET", '/' + path, None, headers)
36 36 response = conn.getresponse()
37 37 print response.status, reasons.get(response.reason, response.reason)
38 38 if show[:1] == ['-']:
39 39 show = sorted(h for h, v in response.getheaders()
40 40 if h.lower() not in show)
41 41 for h in [h.lower() for h in show]:
42 42 if response.getheader(h, None) is not None:
43 43 print "%s: %s" % (h, response.getheader(h))
44 44 if not headeronly:
45 45 print
46 if response.status != 500:
47 data = response.read()
48 sys.stdout.write(data)
46 data = response.read()
47 sys.stdout.write(data)
49 48
50 49 if twice and response.getheader('ETag', None):
51 50 tag = response.getheader('ETag')
52 51
53 52 return response.status
54 53
55 54 status = request(sys.argv[1], sys.argv[2], sys.argv[3:])
56 55 if twice:
57 56 status = request(sys.argv[1], sys.argv[2], sys.argv[3:])
58 57
59 58 if 200 <= status <= 305:
60 59 sys.exit(0)
61 60 sys.exit(1)
@@ -1,582 +1,608 b''
1 1 #require serve
2 2
3 3 Some tests for hgweb. Tests static files, plain files and different 404's.
4 4
5 5 $ hg init test
6 6 $ cd test
7 7 $ mkdir da
8 8 $ echo foo > da/foo
9 9 $ echo foo > foo
10 10 $ hg ci -Ambase
11 11 adding da/foo
12 12 adding foo
13 13 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
14 14 $ cat hg.pid >> $DAEMON_PIDS
15 15
16 16 manifest
17 17
18 18 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
19 19 200 Script output follows
20 20
21 21
22 22 drwxr-xr-x da
23 23 -rw-r--r-- 4 foo
24 24
25 25
26 26 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/da?style=raw')
27 27 200 Script output follows
28 28
29 29
30 30 -rw-r--r-- 4 foo
31 31
32 32
33 33
34 34 plain file
35 35
36 36 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?style=raw'
37 37 200 Script output follows
38 38
39 39 foo
40 40
41 41 should give a 404 - static file that does not exist
42 42
43 43 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'static/bogus'
44 44 404 Not Found
45 45
46 46 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
47 47 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
48 48 <head>
49 49 <link rel="icon" href="/static/hgicon.png" type="image/png" />
50 50 <meta name="robots" content="index, nofollow" />
51 51 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
52 52 <script type="text/javascript" src="/static/mercurial.js"></script>
53 53
54 54 <title>test: error</title>
55 55 </head>
56 56 <body>
57 57
58 58 <div class="container">
59 59 <div class="menu">
60 60 <div class="logo">
61 61 <a href="http://mercurial.selenic.com/">
62 62 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
63 63 </div>
64 64 <ul>
65 65 <li><a href="/shortlog">log</a></li>
66 66 <li><a href="/graph">graph</a></li>
67 67 <li><a href="/tags">tags</a></li>
68 68 <li><a href="/bookmarks">bookmarks</a></li>
69 69 <li><a href="/branches">branches</a></li>
70 70 </ul>
71 71 <ul>
72 72 <li><a href="/help">help</a></li>
73 73 </ul>
74 74 </div>
75 75
76 76 <div class="main">
77 77
78 78 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
79 79 <h3>error</h3>
80 80
81 81 <form class="search" action="/log">
82 82
83 83 <p><input name="rev" id="search1" type="text" size="30"></p>
84 84 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
85 85 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
86 86 </form>
87 87
88 88 <div class="description">
89 89 <p>
90 90 An error occurred while processing your request:
91 91 </p>
92 92 <p>
93 93 Not Found
94 94 </p>
95 95 </div>
96 96 </div>
97 97 </div>
98 98
99 99 <script type="text/javascript">process_dates()</script>
100 100
101 101
102 102 </body>
103 103 </html>
104 104
105 105 [1]
106 106
107 107 should give a 404 - bad revision
108 108
109 109 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/spam/foo?style=raw'
110 110 404 Not Found
111 111
112 112
113 113 error: revision not found: spam
114 114 [1]
115 115
116 116 should give a 400 - bad command
117 117
118 118 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
119 119 400* (glob)
120 120
121 121
122 122 error: no such method: spam
123 123 [1]
124 124
125 125 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT '?cmd=spam'
126 126 400 no such method: spam
127 127 [1]
128 128
129 129 should give a 400 - bad command as a part of url path (issue4071)
130 130
131 131 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam'
132 132 400 no such method: spam
133 133 [1]
134 134
135 135 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'raw-spam'
136 136 400 no such method: spam
137 137 [1]
138 138
139 139 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam/tip/foo'
140 140 400 no such method: spam
141 141 [1]
142 142
143 143 should give a 404 - file does not exist
144 144
145 145 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork?style=raw'
146 146 404 Not Found
147 147
148 148
149 149 error: bork@2ef0ac749a14: not found in manifest
150 150 [1]
151 151 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork'
152 152 404 Not Found
153 153
154 154 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
155 155 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
156 156 <head>
157 157 <link rel="icon" href="/static/hgicon.png" type="image/png" />
158 158 <meta name="robots" content="index, nofollow" />
159 159 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
160 160 <script type="text/javascript" src="/static/mercurial.js"></script>
161 161
162 162 <title>test: error</title>
163 163 </head>
164 164 <body>
165 165
166 166 <div class="container">
167 167 <div class="menu">
168 168 <div class="logo">
169 169 <a href="http://mercurial.selenic.com/">
170 170 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
171 171 </div>
172 172 <ul>
173 173 <li><a href="/shortlog">log</a></li>
174 174 <li><a href="/graph">graph</a></li>
175 175 <li><a href="/tags">tags</a></li>
176 176 <li><a href="/bookmarks">bookmarks</a></li>
177 177 <li><a href="/branches">branches</a></li>
178 178 </ul>
179 179 <ul>
180 180 <li><a href="/help">help</a></li>
181 181 </ul>
182 182 </div>
183 183
184 184 <div class="main">
185 185
186 186 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
187 187 <h3>error</h3>
188 188
189 189 <form class="search" action="/log">
190 190
191 191 <p><input name="rev" id="search1" type="text" size="30"></p>
192 192 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
193 193 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
194 194 </form>
195 195
196 196 <div class="description">
197 197 <p>
198 198 An error occurred while processing your request:
199 199 </p>
200 200 <p>
201 201 bork@2ef0ac749a14: not found in manifest
202 202 </p>
203 203 </div>
204 204 </div>
205 205 </div>
206 206
207 207 <script type="text/javascript">process_dates()</script>
208 208
209 209
210 210 </body>
211 211 </html>
212 212
213 213 [1]
214 214 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/bork?style=raw'
215 215 404 Not Found
216 216
217 217
218 218 error: bork@2ef0ac749a14: not found in manifest
219 219 [1]
220 220
221 221 try bad style
222 222
223 223 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar')
224 224 200 Script output follows
225 225
226 226 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
227 227 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
228 228 <head>
229 229 <link rel="icon" href="/static/hgicon.png" type="image/png" />
230 230 <meta name="robots" content="index, nofollow" />
231 231 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
232 232 <script type="text/javascript" src="/static/mercurial.js"></script>
233 233
234 234 <title>test: 2ef0ac749a14 /</title>
235 235 </head>
236 236 <body>
237 237
238 238 <div class="container">
239 239 <div class="menu">
240 240 <div class="logo">
241 241 <a href="http://mercurial.selenic.com/">
242 242 <img src="/static/hglogo.png" alt="mercurial" /></a>
243 243 </div>
244 244 <ul>
245 245 <li><a href="/shortlog/2ef0ac749a14">log</a></li>
246 246 <li><a href="/graph/2ef0ac749a14">graph</a></li>
247 247 <li><a href="/tags">tags</a></li>
248 248 <li><a href="/bookmarks">bookmarks</a></li>
249 249 <li><a href="/branches">branches</a></li>
250 250 </ul>
251 251 <ul>
252 252 <li><a href="/rev/2ef0ac749a14">changeset</a></li>
253 253 <li class="active">browse</li>
254 254 </ul>
255 255 <ul>
256 256
257 257 </ul>
258 258 <ul>
259 259 <li><a href="/help">help</a></li>
260 260 </ul>
261 261 </div>
262 262
263 263 <div class="main">
264 264 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
265 265 <h3>directory / @ 0:2ef0ac749a14 <span class="tag">tip</span> </h3>
266 266
267 267 <form class="search" action="/log">
268 268
269 269 <p><input name="rev" id="search1" type="text" size="30" /></p>
270 270 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
271 271 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
272 272 </form>
273 273
274 274 <table class="bigtable">
275 275 <tr>
276 276 <th class="name">name</th>
277 277 <th class="size">size</th>
278 278 <th class="permissions">permissions</th>
279 279 </tr>
280 280 <tbody class="stripes2">
281 281 <tr class="fileline">
282 282 <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
283 283 <td class="size"></td>
284 284 <td class="permissions">drwxr-xr-x</td>
285 285 </tr>
286 286
287 287 <tr class="fileline">
288 288 <td class="name">
289 289 <a href="/file/2ef0ac749a14/da">
290 290 <img src="/static/coal-folder.png" alt="dir."/> da/
291 291 </a>
292 292 <a href="/file/2ef0ac749a14/da/">
293 293
294 294 </a>
295 295 </td>
296 296 <td class="size"></td>
297 297 <td class="permissions">drwxr-xr-x</td>
298 298 </tr>
299 299
300 300 <tr class="fileline">
301 301 <td class="filename">
302 302 <a href="/file/2ef0ac749a14/foo">
303 303 <img src="/static/coal-file.png" alt="file"/> foo
304 304 </a>
305 305 </td>
306 306 <td class="size">4</td>
307 307 <td class="permissions">-rw-r--r--</td>
308 308 </tr>
309 309 </tbody>
310 310 </table>
311 311 </div>
312 312 </div>
313 313 <script type="text/javascript">process_dates()</script>
314 314
315 315
316 316 </body>
317 317 </html>
318 318
319 319
320 320 stop and restart
321 321
322 322 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
323 323 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
324 324 $ cat hg.pid >> $DAEMON_PIDS
325 325
326 326 Test the access/error files are opened in append mode
327 327
328 328 $ $PYTHON -c "print len(file('access.log').readlines()), 'log lines written'"
329 329 14 log lines written
330 330
331 331 static file
332 332
333 333 $ "$TESTDIR/get-with-headers.py" --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
334 334 200 Script output follows
335 335 content-length: 5262
336 336 content-type: text/css
337 337
338 338 body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; }
339 339 a { color:#0000cc; }
340 340 a:hover, a:visited, a:active { color:#880000; }
341 341 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
342 342 div.page_header a:visited { color:#0000cc; }
343 343 div.page_header a:hover { color:#880000; }
344 344 div.page_nav { padding:8px; }
345 345 div.page_nav a:visited { color:#0000cc; }
346 346 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
347 347 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
348 348 div.page_footer_text { float:left; color:#555555; font-style:italic; }
349 349 div.page_body { padding:8px; }
350 350 div.title, a.title {
351 351 display:block; padding:6px 8px;
352 352 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
353 353 }
354 354 a.title:hover { background-color: #d9d8d1; }
355 355 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
356 356 div.log_body { padding:8px 8px 8px 150px; }
357 357 .age { white-space:nowrap; }
358 358 span.age { position:relative; float:left; width:142px; font-style:italic; }
359 359 div.log_link {
360 360 padding:0px 8px;
361 361 font-size:10px; font-family:sans-serif; font-style:normal;
362 362 position:relative; float:left; width:136px;
363 363 }
364 364 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
365 365 a.list { text-decoration:none; color:#000000; }
366 366 a.list:hover { text-decoration:underline; color:#880000; }
367 367 table { padding:8px 4px; }
368 368 th { padding:2px 5px; font-size:12px; text-align:left; }
369 369 tr.light:hover, .parity0:hover { background-color:#edece6; }
370 370 tr.dark, .parity1 { background-color:#f6f6f0; }
371 371 tr.dark:hover, .parity1:hover { background-color:#edece6; }
372 372 td { padding:2px 5px; font-size:12px; vertical-align:top; }
373 373 td.closed { background-color: #99f; }
374 374 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
375 375 td.indexlinks { white-space: nowrap; }
376 376 td.indexlinks a {
377 377 padding: 2px 5px; line-height: 10px;
378 378 border: 1px solid;
379 379 color: #ffffff; background-color: #7777bb;
380 380 border-color: #aaaadd #333366 #333366 #aaaadd;
381 381 font-weight: bold; text-align: center; text-decoration: none;
382 382 font-size: 10px;
383 383 }
384 384 td.indexlinks a:hover { background-color: #6666aa; }
385 385 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
386 386 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
387 387 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
388 388 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
389 389 .linenr { color:#999999; text-decoration:none }
390 390 div.rss_logo { float: right; white-space: nowrap; }
391 391 div.rss_logo a {
392 392 padding:3px 6px; line-height:10px;
393 393 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
394 394 color:#ffffff; background-color:#ff6600;
395 395 font-weight:bold; font-family:sans-serif; font-size:10px;
396 396 text-align:center; text-decoration:none;
397 397 }
398 398 div.rss_logo a:hover { background-color:#ee5500; }
399 399 pre { margin: 0; }
400 400 span.logtags span {
401 401 padding: 0px 4px;
402 402 font-size: 10px;
403 403 font-weight: normal;
404 404 border: 1px solid;
405 405 background-color: #ffaaff;
406 406 border-color: #ffccff #ff00ee #ff00ee #ffccff;
407 407 }
408 408 span.logtags span.tagtag {
409 409 background-color: #ffffaa;
410 410 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
411 411 }
412 412 span.logtags span.branchtag {
413 413 background-color: #aaffaa;
414 414 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
415 415 }
416 416 span.logtags span.inbranchtag {
417 417 background-color: #d5dde6;
418 418 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
419 419 }
420 420 span.logtags span.bookmarktag {
421 421 background-color: #afdffa;
422 422 border-color: #ccecff #46ace6 #46ace6 #ccecff;
423 423 }
424 424
425 425 /* Graph */
426 426 div#wrapper {
427 427 position: relative;
428 428 margin: 0;
429 429 padding: 0;
430 430 margin-top: 3px;
431 431 }
432 432
433 433 canvas {
434 434 position: absolute;
435 435 z-index: 5;
436 436 top: -0.9em;
437 437 margin: 0;
438 438 }
439 439
440 440 ul#nodebgs {
441 441 list-style: none inside none;
442 442 padding: 0;
443 443 margin: 0;
444 444 top: -0.7em;
445 445 }
446 446
447 447 ul#graphnodes li, ul#nodebgs li {
448 448 height: 39px;
449 449 }
450 450
451 451 ul#graphnodes {
452 452 position: absolute;
453 453 z-index: 10;
454 454 top: -0.8em;
455 455 list-style: none inside none;
456 456 padding: 0;
457 457 }
458 458
459 459 ul#graphnodes li .info {
460 460 display: block;
461 461 font-size: 100%;
462 462 position: relative;
463 463 top: -3px;
464 464 font-style: italic;
465 465 }
466 466
467 467 /* Comparison */
468 468 .legend {
469 469 padding: 1.5% 0 1.5% 0;
470 470 }
471 471
472 472 .legendinfo {
473 473 border: 1px solid #d9d8d1;
474 474 font-size: 80%;
475 475 text-align: center;
476 476 padding: 0.5%;
477 477 }
478 478
479 479 .equal {
480 480 background-color: #ffffff;
481 481 }
482 482
483 483 .delete {
484 484 background-color: #faa;
485 485 color: #333;
486 486 }
487 487
488 488 .insert {
489 489 background-color: #ffa;
490 490 }
491 491
492 492 .replace {
493 493 background-color: #e8e8e8;
494 494 }
495 495
496 496 .comparison {
497 497 overflow-x: auto;
498 498 }
499 499
500 500 .header th {
501 501 text-align: center;
502 502 }
503 503
504 504 .block {
505 505 border-top: 1px solid #d9d8d1;
506 506 }
507 507
508 508 .scroll-loading {
509 509 -webkit-animation: change_color 1s linear 0s infinite alternate;
510 510 -moz-animation: change_color 1s linear 0s infinite alternate;
511 511 -o-animation: change_color 1s linear 0s infinite alternate;
512 512 animation: change_color 1s linear 0s infinite alternate;
513 513 }
514 514
515 515 @-webkit-keyframes change_color {
516 516 from { background-color: #A0CEFF; } to { }
517 517 }
518 518 @-moz-keyframes change_color {
519 519 from { background-color: #A0CEFF; } to { }
520 520 }
521 521 @-o-keyframes change_color {
522 522 from { background-color: #A0CEFF; } to { }
523 523 }
524 524 @keyframes change_color {
525 525 from { background-color: #A0CEFF; } to { }
526 526 }
527 527
528 528 .scroll-loading-error {
529 529 background-color: #FFCCCC !important;
530 530 }
531 531 304 Not Modified
532 532
533 533
534 534 phase changes are refreshed (issue4061)
535 535
536 536 $ echo bar >> foo
537 537 $ hg ci -msecret --secret
538 538 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
539 539 200 Script output follows
540 540
541 541
542 542 # HG changelog
543 543 # Node ID 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
544 544
545 545 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
546 546 revision: 0
547 547 user: test
548 548 date: Thu, 01 Jan 1970 00:00:00 +0000
549 549 summary: base
550 550 branch: default
551 551 tag: tip
552 552
553 553
554 554 $ hg phase --draft tip
555 555 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
556 556 200 Script output follows
557 557
558 558
559 559 # HG changelog
560 560 # Node ID a084749e708a9c4c0a5b652a2a446322ce290e04
561 561
562 562 changeset: a084749e708a9c4c0a5b652a2a446322ce290e04
563 563 revision: 1
564 564 user: test
565 565 date: Thu, 01 Jan 1970 00:00:00 +0000
566 566 summary: secret
567 567 branch: default
568 568 tag: tip
569 569
570 570 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
571 571 revision: 0
572 572 user: test
573 573 date: Thu, 01 Jan 1970 00:00:00 +0000
574 574 summary: base
575 575
576 576
577 577
578 578 errors
579 579
580 580 $ cat errors.log
581 581
582 Uncaught exceptions result in a logged error and canned HTTP response
583
584 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
585 $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
586 $ cat hg.pid >> $DAEMON_PIDS
587
588 $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
589 500 Internal Server Error
590 transfer-encoding: chunked
591
592 Internal Server Error (no-eol)
593 [1]
594
595 $ head -1 errors.log
596 .* Exception happened during processing request '/raiseerror': (re)
597
598 Uncaught exception after partial content sent
599
600 $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
601 200 Script output follows
602 transfer-encoding: chunked
603 content-type: text/plain
604
605 partial content
606 Internal Server Error (no-eol)
607
582 608 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now