##// END OF EJS Templates
hgweb: return meaningful HTTP status codes instead of nonsense
Bryan O'Sullivan -
r5561:22713dce default
parent child Browse files
Show More
@@ -0,0 +1,55
1 #!/bin/sh
2
3 mkdir webdir
4 cd webdir
5
6 hg init a
7 echo a > a/a
8 hg --cwd a ci -Ama -d'1 0'
9
10 hg init b
11 echo b > b/b
12 hg --cwd b ci -Amb -d'2 0'
13
14 hg init c
15 echo c > c/c
16 hg --cwd c ci -Amc -d'3 0'
17 root=`pwd`
18
19 cd ..
20
21 cat > paths.conf <<EOF
22 [paths]
23 a=$root/a
24 b=$root/b
25 EOF
26
27 hg serve -p $HGPORT -d --pid-file=hg.pid --webdir-conf paths.conf \
28 -A access-paths.log -E error-paths.log
29 cat hg.pid >> $DAEMON_PIDS
30
31 echo % should give a 404 - file does not exist
32 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/bork?style=raw'
33
34 echo % should succeed
35 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/?style=raw'
36 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/a?style=raw'
37 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/b/file/tip/b?style=raw'
38
39 echo % should give a 404 - repo is not published
40 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/c/file/tip/c?style=raw'
41
42 cat > collections.conf <<EOF
43 [collections]
44 $root=$root
45 EOF
46
47 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf collections.conf \
48 -A access-collections.log -E error-collections.log
49 cat hg.pid >> $DAEMON_PIDS
50
51 echo % should succeed
52 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
53 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/a/file/tip/a?style=raw'
54 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/b/file/tip/b?style=raw'
55 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/c/file/tip/c?style=raw'
@@ -0,0 +1,43
1 adding a
2 adding b
3 adding c
4 % should give a 404 - file does not exist
5 404 Not Found
6
7
8 error: Path not found: bork/
9 % should succeed
10 200 Script output follows
11
12
13 /a/
14 /b/
15
16 200 Script output follows
17
18 a
19 200 Script output follows
20
21 b
22 % should give a 404 - repo is not published
23 404 Not Found
24
25
26 error: repository c not found
27 % should succeed
28 200 Script output follows
29
30
31 /a/
32 /b/
33 /c/
34
35 200 Script output follows
36
37 a
38 200 Script output follows
39
40 b
41 200 Script output follows
42
43 c
@@ -6,7 +6,17
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 import os, mimetypes
9 import errno, mimetypes, os
10
11 class ErrorResponse(Exception):
12 def __init__(self, code, message=None):
13 Exception.__init__(self)
14 self.code = code
15 if message is None:
16 from httplib import responses
17 self.message = responses.get(code, 'Error')
18 else:
19 self.message = message
10
20
11 def get_mtime(repo_path):
21 def get_mtime(repo_path):
12 store_path = os.path.join(repo_path, ".hg")
22 store_path = os.path.join(repo_path, ".hg")
@@ -40,9 +50,13 def staticfile(directory, fname, req):
40 req.header([('Content-type', ct),
50 req.header([('Content-type', ct),
41 ('Content-length', str(os.path.getsize(path)))])
51 ('Content-length', str(os.path.getsize(path)))])
42 return file(path, 'rb').read()
52 return file(path, 'rb').read()
43 except (TypeError, OSError):
53 except TypeError:
44 # illegal fname or unreadable file
54 raise ErrorResponse(500, 'illegal file name')
45 return ""
55 except OSError, err:
56 if err.errno == errno.ENOENT:
57 raise ErrorResponse(404)
58 else:
59 raise ErrorResponse(500, err.strerror)
46
60
47 def style_map(templatepath, style):
61 def style_map(templatepath, style):
48 """Return path to mapfile for a given style.
62 """Return path to mapfile for a given style.
@@ -6,13 +6,13
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 import os, mimetypes, re, zlib, mimetools, cStringIO, sys
9 import errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys
10 import tempfile, urllib, bz2
10 import tempfile, urllib, bz2
11 from mercurial.node import *
11 from mercurial.node import *
12 from mercurial.i18n import gettext as _
12 from mercurial.i18n import gettext as _
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 from mercurial import revlog, templater
14 from mercurial import revlog, templater
15 from common import get_mtime, staticfile, style_map, paritygen
15 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
16
16
17 def _up(p):
17 def _up(p):
18 if p[0] != "/":
18 if p[0] != "/":
@@ -478,6 +478,9 class hgweb(object):
478 short = os.path.basename(remain)
478 short = os.path.basename(remain)
479 files[short] = (f, n)
479 files[short] = (f, n)
480
480
481 if not files:
482 raise ErrorResponse(404, 'Path not found: ' + path)
483
481 def filelist(**map):
484 def filelist(**map):
482 fl = files.keys()
485 fl = files.keys()
483 fl.sort()
486 fl.sort()
@@ -845,14 +848,20 class hgweb(object):
845
848
846 cmd = req.form['cmd'][0]
849 cmd = req.form['cmd'][0]
847
850
848 method = getattr(self, 'do_' + cmd, None)
849 if method:
850 try:
851 try:
852 method = getattr(self, 'do_' + cmd)
851 method(req)
853 method(req)
854 except revlog.LookupError, err:
855 req.respond(404, self.t(
856 'error', error='revision not found: %s' % err.name))
852 except (hg.RepoError, revlog.RevlogError), inst:
857 except (hg.RepoError, revlog.RevlogError), inst:
853 req.write(self.t("error", error=str(inst)))
858 req.respond('500 Internal Server Error',
854 else:
859 self.t('error', error=str(inst)))
855 req.write(self.t("error", error='No such method: ' + cmd))
860 except ErrorResponse, inst:
861 req.respond(inst.code, self.t('error', error=inst.message))
862 except AttributeError:
863 req.respond(400,
864 self.t('error', error='No such method: ' + cmd))
856 finally:
865 finally:
857 self.t = None
866 self.t = None
858
867
@@ -1038,7 +1047,8 class hgweb(object):
1038 self.archive(req, req.form['node'][0], type_)
1047 self.archive(req, req.form['node'][0], type_)
1039 return
1048 return
1040
1049
1041 req.write(self.t("error"))
1050 req.respond(400, self.t('error',
1051 error='Unsupported archive type: %s' % type_))
1042
1052
1043 def do_static(self, req):
1053 def do_static(self, req):
1044 fname = req.form['file'][0]
1054 fname = req.form['file'][0]
@@ -1047,8 +1057,7 class hgweb(object):
1047 static = self.config("web", "static",
1057 static = self.config("web", "static",
1048 os.path.join(self.templatepath, "static"),
1058 os.path.join(self.templatepath, "static"),
1049 untrusted=False)
1059 untrusted=False)
1050 req.write(staticfile(static, fname, req)
1060 req.write(staticfile(static, fname, req))
1051 or self.t("error", error="%r not found" % fname))
1052
1061
1053 def do_capabilities(self, req):
1062 def do_capabilities(self, req):
1054 caps = ['lookup', 'changegroupsubset']
1063 caps = ['lookup', 'changegroupsubset']
@@ -1198,7 +1207,11 class hgweb(object):
1198 else:
1207 else:
1199 filename = ''
1208 filename = ''
1200 error = getattr(inst, 'strerror', 'Unknown error')
1209 error = getattr(inst, 'strerror', 'Unknown error')
1201 req.write('%s: %s\n' % (error, filename))
1210 if inst.errno == errno.ENOENT:
1211 code = 404
1212 else:
1213 code = 500
1214 req.respond(code, '%s: %s\n' % (error, filename))
1202 finally:
1215 finally:
1203 fp.close()
1216 fp.close()
1204 os.unlink(tempname)
1217 os.unlink(tempname)
@@ -9,7 +9,7
9 import os, mimetools, cStringIO
9 import os, mimetools, cStringIO
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial import ui, hg, util, templater
11 from mercurial import ui, hg, util, templater
12 from common import get_mtime, staticfile, style_map, paritygen
12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
13 from hgweb_mod import hgweb
13 from hgweb_mod import hgweb
14
14
15 # This is a stopgap
15 # This is a stopgap
@@ -215,12 +215,12 class hgwebdir(object):
215 **dict(sort)))
215 **dict(sort)))
216
216
217 try:
217 try:
218 try:
218 virtual = req.env.get("PATH_INFO", "").strip('/')
219 virtual = req.env.get("PATH_INFO", "").strip('/')
219 if virtual.startswith('static/'):
220 if virtual.startswith('static/'):
220 static = os.path.join(templater.templatepath(), 'static')
221 static = os.path.join(templater.templatepath(), 'static')
221 fname = virtual[7:]
222 fname = virtual[7:]
222 req.write(staticfile(static, fname, req) or
223 req.write(staticfile(static, fname, req))
223 tmpl('error', error='%r not found' % fname))
224 elif virtual:
224 elif virtual:
225 repos = dict(self.repos)
225 repos = dict(self.repos)
226 while virtual:
226 while virtual:
@@ -230,11 +230,11 class hgwebdir(object):
230 try:
230 try:
231 repo = hg.repository(parentui, real)
231 repo = hg.repository(parentui, real)
232 hgweb(repo).run_wsgi(req)
232 hgweb(repo).run_wsgi(req)
233 return
233 except IOError, inst:
234 except IOError, inst:
234 req.write(tmpl("error", error=inst.strerror))
235 raise ErrorResponse(500, inst.strerror)
235 except hg.RepoError, inst:
236 except hg.RepoError, inst:
236 req.write(tmpl("error", error=str(inst)))
237 raise ErrorResponse(500, str(inst))
237 return
238
238
239 # browse subdirectories
239 # browse subdirectories
240 subdir = virtual + '/'
240 subdir = virtual + '/'
@@ -247,14 +247,15 class hgwebdir(object):
247 break
247 break
248 virtual = virtual[:up]
248 virtual = virtual[:up]
249
249
250 req.write(tmpl("notfound", repo=virtual))
250 req.respond(404, tmpl("notfound", repo=virtual))
251 else:
251 else:
252 if req.form.has_key('static'):
252 if req.form.has_key('static'):
253 static = os.path.join(templater.templatepath(), "static")
253 static = os.path.join(templater.templatepath(), "static")
254 fname = req.form['static'][0]
254 fname = req.form['static'][0]
255 req.write(staticfile(static, fname, req)
255 req.write(staticfile(static, fname, req))
256 or tmpl("error", error="%r not found" % fname))
257 else:
256 else:
258 makeindex(req)
257 makeindex(req)
258 except ErrorResponse, err:
259 req.respond(err.code, tmpl('error', error=err.message or ''))
259 finally:
260 finally:
260 tmpl = None
261 tmpl = None
@@ -8,6 +8,7
8
8
9 import socket, cgi, errno
9 import socket, cgi, errno
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from common import ErrorResponse
11
12
12 class wsgiapplication(object):
13 class wsgiapplication(object):
13 def __init__(self, destmaker):
14 def __init__(self, destmaker):
@@ -42,26 +43,38 class _wsgirequest(object):
42 def read(self, count=-1):
43 def read(self, count=-1):
43 return self.inp.read(count)
44 return self.inp.read(count)
44
45
45 def write(self, *things):
46 def respond(self, status, *things):
46 for thing in things:
47 for thing in things:
47 if hasattr(thing, "__iter__"):
48 if hasattr(thing, "__iter__"):
48 for part in thing:
49 for part in thing:
49 self.write(part)
50 self.respond(status, part)
50 else:
51 else:
51 thing = str(thing)
52 thing = str(thing)
52 if self.server_write is None:
53 if self.server_write is None:
53 if not self.headers:
54 if not self.headers:
54 raise RuntimeError("request.write called before headers sent (%s)." % thing)
55 raise RuntimeError("request.write called before headers sent (%s)." % thing)
55 self.server_write = self.start_response('200 Script output follows',
56 code = None
57 if isinstance(status, ErrorResponse):
58 code = status.code
59 elif isinstance(status, int):
60 code = status
61 if code:
62 from httplib import responses
63 status = '%d %s' % (
64 code, responses.get(code, 'Error'))
65 self.server_write = self.start_response(status,
56 self.headers)
66 self.headers)
57 self.start_response = None
67 self.start_response = None
58 self.headers = None
68 self.headers = []
59 try:
69 try:
60 self.server_write(thing)
70 self.server_write(thing)
61 except socket.error, inst:
71 except socket.error, inst:
62 if inst[0] != errno.ECONNRESET:
72 if inst[0] != errno.ECONNRESET:
63 raise
73 raise
64
74
75 def write(self, *things):
76 self.respond('200 Script output follows', *things)
77
65 def writelines(self, lines):
78 def writelines(self, lines):
66 for line in lines:
79 for line in lines:
67 self.write(line)
80 self.write(line)
@@ -14,3 +14,7 for h in headers:
14 print "%s: %s" % (h, response.getheader(h))
14 print "%s: %s" % (h, response.getheader(h))
15 print
15 print
16 sys.stdout.write(response.read())
16 sys.stdout.write(response.read())
17
18 if 200 <= response.status <= 299:
19 sys.exit(0)
20 sys.exit(1)
@@ -7,7 +7,25 echo foo > da/foo
7 echo foo > foo
7 echo foo > foo
8 hg ci -Ambase -d '0 0'
8 hg ci -Ambase -d '0 0'
9 hg serve -p $HGPORT -d --pid-file=hg.pid
9 hg serve -p $HGPORT -d --pid-file=hg.pid
10 cat hg.pid >> $DAEMON_PIDS
10 echo % manifest
11 echo % manifest
11 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/?style=raw')
12 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/?style=raw')
12 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/da?style=raw')
13 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/da?style=raw')
13 kill `cat hg.pid`
14
15 echo % plain file
16 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/foo?style=raw'
17
18 echo % should give a 404 - static file that does not exist
19 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/static/bogus'
20
21 echo % should give a 404 - bad revision
22 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/spam/foo?style=raw'
23
24 echo % should give a 400 - bad command
25 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/foo?cmd=spam&style=raw'
26
27 echo % should give a 404 - file does not exist
28 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/bork?style=raw'
29
30 echo % static file
31 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/static/style-gitweb.css'
@@ -14,3 +14,123 200 Script output follows
14 -rw-r--r-- 4 foo
14 -rw-r--r-- 4 foo
15
15
16
16
17 % plain file
18 200 Script output follows
19
20 foo
21 % should give a 404 - static file that does not exist
22 404 Not Found
23
24 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
25 <html>
26 <head>
27 <link rel="icon" href="/static/hgicon.png" type="image/png">
28 <meta name="robots" content="index, nofollow" />
29 <link rel="stylesheet" href="/static/style.css" type="text/css" />
30
31 <title>Mercurial Error</title>
32 </head>
33 <body>
34
35 <h2>Mercurial Error</h2>
36
37 <p>
38 An error occured while processing your request:
39 </p>
40 <p>
41 Not Found
42 </p>
43
44
45 <div class="logo">
46 powered by<br/>
47 <a href="http://www.selenic.com/mercurial/">mercurial</a>
48 </div>
49
50 </body>
51 </html>
52
53 % should give a 404 - bad revision
54 404 Not Found
55
56
57 error: revision not found: spam
58 % should give a 400 - bad command
59 400 Bad Request
60
61
62 error: No such method: spam
63 % should give a 404 - file does not exist
64 404 Not Found
65
66
67 error: Path not found: bork/
68 % static file
69 200 Script output follows
70
71 body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; }
72 a { color:#0000cc; }
73 a:hover, a:visited, a:active { color:#880000; }
74 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
75 div.page_header a:visited { color:#0000cc; }
76 div.page_header a:hover { color:#880000; }
77 div.page_nav { padding:8px; }
78 div.page_nav a:visited { color:#0000cc; }
79 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
80 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
81 div.page_footer_text { float:left; color:#555555; font-style:italic; }
82 div.page_body { padding:8px; }
83 div.title, a.title {
84 display:block; padding:6px 8px;
85 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
86 }
87 a.title:hover { background-color: #d9d8d1; }
88 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
89 div.log_body { padding:8px 8px 8px 150px; }
90 .age { white-space:nowrap; }
91 span.age { position:relative; float:left; width:142px; font-style:italic; }
92 div.log_link {
93 padding:0px 8px;
94 font-size:10px; font-family:sans-serif; font-style:normal;
95 position:relative; float:left; width:136px;
96 }
97 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
98 a.list { text-decoration:none; color:#000000; }
99 a.list:hover { text-decoration:underline; color:#880000; }
100 table { padding:8px 4px; }
101 th { padding:2px 5px; font-size:12px; text-align:left; }
102 tr.light:hover, .parity0:hover { background-color:#edece6; }
103 tr.dark, .parity1 { background-color:#f6f6f0; }
104 tr.dark:hover, .parity1:hover { background-color:#edece6; }
105 td { padding:2px 5px; font-size:12px; vertical-align:top; }
106 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
107 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
108 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
109 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
110 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
111 .linenr { color:#999999; text-decoration:none }
112 a.rss_logo {
113 float:right; padding:3px 0px; width:35px; line-height:10px;
114 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
115 color:#ffffff; background-color:#ff6600;
116 font-weight:bold; font-family:sans-serif; font-size:10px;
117 text-align:center; text-decoration:none;
118 }
119 a.rss_logo:hover { background-color:#ee5500; }
120 pre { margin: 0; }
121 span.logtags span {
122 padding: 0px 4px;
123 font-size: 10px;
124 font-weight: normal;
125 border: 1px solid;
126 background-color: #ffaaff;
127 border-color: #ffccff #ff00ee #ff00ee #ffccff;
128 }
129 span.logtags span.tagtag {
130 background-color: #ffffaa;
131 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
132 }
133 span.logtags span.branchtag {
134 background-color: #aaffaa;
135 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
136 }
General Comments 0
You need to be logged in to leave comments. Login now