##// END OF EJS Templates
hgweb: explicit response status
Dirkjan Ochtman -
r5993:948a41e7 default
parent child Browse files
Show More
@@ -8,6 +8,11 b''
8
8
9 import errno, mimetypes, os
9 import errno, mimetypes, os
10
10
11 HTTP_OK = 200
12 HTTP_BAD_REQUEST = 400
13 HTTP_NOT_FOUND = 404
14 HTTP_SERVER_ERROR = 500
15
11 class ErrorResponse(Exception):
16 class ErrorResponse(Exception):
12 def __init__(self, code, message=None):
17 def __init__(self, code, message=None):
13 Exception.__init__(self)
18 Exception.__init__(self)
@@ -54,18 +59,15 b' def staticfile(directory, fname, req):'
54 try:
59 try:
55 os.stat(path)
60 os.stat(path)
56 ct = mimetypes.guess_type(path)[0] or "text/plain"
61 ct = mimetypes.guess_type(path)[0] or "text/plain"
57 req.header([
62 req.respond(HTTP_OK, ct, length = os.path.getsize(path))
58 ('Content-Type', ct),
59 ('Content-Length', str(os.path.getsize(path)))
60 ])
61 return file(path, 'rb').read()
63 return file(path, 'rb').read()
62 except TypeError:
64 except TypeError:
63 raise ErrorResponse(500, 'illegal file name')
65 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal file name')
64 except OSError, err:
66 except OSError, err:
65 if err.errno == errno.ENOENT:
67 if err.errno == errno.ENOENT:
66 raise ErrorResponse(404)
68 raise ErrorResponse(HTTP_NOT_FOUND)
67 else:
69 else:
68 raise ErrorResponse(500, err.strerror)
70 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
69
71
70 def style_map(templatepath, style):
72 def style_map(templatepath, style):
71 """Return path to mapfile for a given style.
73 """Return path to mapfile for a given style.
@@ -11,6 +11,7 b' from mercurial.node import *'
11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 from mercurial import revlog, templater, templatefilters
12 from mercurial import revlog, templater, templatefilters
13 from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact
13 from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact
14 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 from request import wsgirequest
15 from request import wsgirequest
15 import webcommands, protocol
16 import webcommands, protocol
16
17
@@ -207,27 +208,35 b' class hgweb(object):'
207 method(self, req)
208 method(self, req)
208 else:
209 else:
209 tmpl = self.templater(req)
210 tmpl = self.templater(req)
211 ctype = tmpl('mimetype', encoding=self.encoding)
212 ctype = templater.stringify(ctype)
213
210 if cmd == '':
214 if cmd == '':
211 req.form['cmd'] = [tmpl.cache['default']]
215 req.form['cmd'] = [tmpl.cache['default']]
212 cmd = req.form['cmd'][0]
216 cmd = req.form['cmd'][0]
213
217
214 if cmd not in webcommands.__all__:
218 if cmd not in webcommands.__all__:
215 raise ErrorResponse(400, 'No such method: ' + cmd)
219 msg = 'No such method: %s' % cmd
220 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
216 elif cmd == 'file' and 'raw' in req.form.get('style', []):
221 elif cmd == 'file' and 'raw' in req.form.get('style', []):
222 self.ctype = ctype
217 content = webcommands.rawfile(self, req, tmpl)
223 content = webcommands.rawfile(self, req, tmpl)
218 else:
224 else:
219 content = getattr(webcommands, cmd)(self, req, tmpl)
225 content = getattr(webcommands, cmd)(self, req, tmpl)
226 req.respond(HTTP_OK, ctype)
220
227
221 req.write(content)
228 req.write(content)
222 del tmpl
229 del tmpl
223
230
224 except revlog.LookupError, err:
231 except revlog.LookupError, err:
225 req.respond(404, tmpl(
232 req.respond(HTTP_NOT_FOUND, ctype)
226 'error', error='revision not found: %s' % err.name))
233 req.write(tmpl('error', error='revision not found: %s' % err.name))
227 except (hg.RepoError, revlog.RevlogError), inst:
234 except (hg.RepoError, revlog.RevlogError), inst:
228 req.respond(500, tmpl('error', error=str(inst)))
235 req.respond(HTTP_SERVER_ERROR, ctype)
236 req.write(tmpl('error', error=str(inst)))
229 except ErrorResponse, inst:
237 except ErrorResponse, inst:
230 req.respond(inst.code, tmpl('error', error=inst.message))
238 req.respond(inst.code, ctype)
239 req.write(tmpl('error', error=inst.message))
231
240
232 def templater(self, req):
241 def templater(self, req):
233
242
@@ -252,8 +261,6 b' class hgweb(object):'
252 # some functions for the templater
261 # some functions for the templater
253
262
254 def header(**map):
263 def header(**map):
255 ctype = tmpl('mimetype', encoding=self.encoding)
256 req.httphdr(templater.stringify(ctype))
257 yield tmpl('header', encoding=self.encoding, **map)
264 yield tmpl('header', encoding=self.encoding, **map)
258
265
259 def footer(**map):
266 def footer(**map):
@@ -668,7 +675,7 b' class hgweb(object):'
668 files[short] = (f, n)
675 files[short] = (f, n)
669
676
670 if not files:
677 if not files:
671 raise ErrorResponse(404, 'Path not found: ' + path)
678 raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
672
679
673 def filelist(**map):
680 def filelist(**map):
674 fl = files.keys()
681 fl = files.keys()
@@ -846,6 +853,7 b' class hgweb(object):'
846 if encoding:
853 if encoding:
847 headers.append(('Content-Encoding', encoding))
854 headers.append(('Content-Encoding', encoding))
848 req.header(headers)
855 req.header(headers)
856 req.respond(HTTP_OK)
849 archival.archive(self.repo, req, cnode, artype, prefix=name)
857 archival.archive(self.repo, req, cnode, artype, prefix=name)
850
858
851 # add tags to things
859 # add tags to things
@@ -10,7 +10,7 b' import os'
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial import ui, hg, util, templater, templatefilters
11 from mercurial import ui, hg, util, templater, templatefilters
12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
13 get_contact
13 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 from hgweb_mod import hgweb
14 from hgweb_mod import hgweb
15 from request import wsgirequest
15 from request import wsgirequest
16
16
@@ -76,6 +76,9 b' class hgwebdir(object):'
76 try:
76 try:
77
77
78 virtual = req.env.get("PATH_INFO", "").strip('/')
78 virtual = req.env.get("PATH_INFO", "").strip('/')
79 tmpl = self.templater(req)
80 ctype = tmpl('mimetype', encoding=util._encoding)
81 ctype = templater.stringify(ctype)
79
82
80 # a static file
83 # a static file
81 if virtual.startswith('static/') or 'static' in req.form:
84 if virtual.startswith('static/') or 'static' in req.form:
@@ -89,11 +92,12 b' class hgwebdir(object):'
89
92
90 # top-level index
93 # top-level index
91 elif not virtual:
94 elif not virtual:
92 tmpl = self.templater(req)
95 req.respond(HTTP_OK, ctype)
93 req.write(self.makeindex(req, tmpl))
96 req.write(self.makeindex(req, tmpl))
94 return
97 return
95
98
96 # nested indexes and hgwebs
99 # nested indexes and hgwebs
100
97 repos = dict(self.repos)
101 repos = dict(self.repos)
98 while virtual:
102 while virtual:
99 real = repos.get(virtual)
103 real = repos.get(virtual)
@@ -104,14 +108,15 b' class hgwebdir(object):'
104 hgweb(repo).run_wsgi(req)
108 hgweb(repo).run_wsgi(req)
105 return
109 return
106 except IOError, inst:
110 except IOError, inst:
107 raise ErrorResponse(500, inst.strerror)
111 msg = inst.strerror
112 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
108 except hg.RepoError, inst:
113 except hg.RepoError, inst:
109 raise ErrorResponse(500, str(inst))
114 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
110
115
111 # browse subdirectories
116 # browse subdirectories
112 subdir = virtual + '/'
117 subdir = virtual + '/'
113 if [r for r in repos if r.startswith(subdir)]:
118 if [r for r in repos if r.startswith(subdir)]:
114 tmpl = self.templater(req)
119 req.respond(HTTP_OK, ctype)
115 req.write(self.makeindex(req, tmpl, subdir))
120 req.write(self.makeindex(req, tmpl, subdir))
116 return
121 return
117
122
@@ -121,12 +126,12 b' class hgwebdir(object):'
121 virtual = virtual[:up]
126 virtual = virtual[:up]
122
127
123 # prefixes not found
128 # prefixes not found
124 tmpl = self.templater(req)
129 req.respond(HTTP_NOT_FOUND, ctype)
125 req.respond(404, tmpl("notfound", repo=virtual))
130 req.write(tmpl("notfound", repo=virtual))
126
131
127 except ErrorResponse, err:
132 except ErrorResponse, err:
128 tmpl = self.templater(req)
133 req.respond(err.code, ctype)
129 req.respond(err.code, tmpl('error', error=err.message or ''))
134 req.write(tmpl('error', error=err.message or ''))
130 finally:
135 finally:
131 tmpl = None
136 tmpl = None
132
137
@@ -234,8 +239,6 b' class hgwebdir(object):'
234 def templater(self, req):
239 def templater(self, req):
235
240
236 def header(**map):
241 def header(**map):
237 ctype = tmpl('mimetype', encoding=util._encoding)
238 req.httphdr(templater.stringify(ctype))
239 yield tmpl('header', encoding=util._encoding, **map)
242 yield tmpl('header', encoding=util._encoding, **map)
240
243
241 def footer(**map):
244 def footer(**map):
@@ -9,6 +9,7 b' import cStringIO, zlib, bz2, tempfile, e'
9 from mercurial import util, streamclone
9 from mercurial import util, streamclone
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial.node import *
11 from mercurial.node import *
12 from common import HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
12
13
13 # __all__ is populated with the allowed commands. Be sure to add to it if
14 # __all__ is populated with the allowed commands. Be sure to add to it if
14 # you're adding a new command, or the new command won't work.
15 # you're adding a new command, or the new command won't work.
@@ -18,6 +19,8 b' from mercurial.node import *'
18 'changegroupsubset', 'capabilities', 'unbundle', 'stream_out',
19 'changegroupsubset', 'capabilities', 'unbundle', 'stream_out',
19 ]
20 ]
20
21
22 HGTYPE = 'application/mercurial-0.1'
23
21 def lookup(web, req):
24 def lookup(web, req):
22 try:
25 try:
23 r = hex(web.repo.lookup(req.form['key'][0]))
26 r = hex(web.repo.lookup(req.form['key'][0]))
@@ -26,12 +29,12 b' def lookup(web, req):'
26 r = str(inst)
29 r = str(inst)
27 success = 0
30 success = 0
28 resp = "%s %s\n" % (success, r)
31 resp = "%s %s\n" % (success, r)
29 req.httphdr("application/mercurial-0.1", length=len(resp))
32 req.respond(HTTP_OK, HGTYPE, length=len(resp))
30 req.write(resp)
33 req.write(resp)
31
34
32 def heads(web, req):
35 def heads(web, req):
33 resp = " ".join(map(hex, web.repo.heads())) + "\n"
36 resp = " ".join(map(hex, web.repo.heads())) + "\n"
34 req.httphdr("application/mercurial-0.1", length=len(resp))
37 req.respond(HTTP_OK, HGTYPE, length=len(resp))
35 req.write(resp)
38 req.write(resp)
36
39
37 def branches(web, req):
40 def branches(web, req):
@@ -42,7 +45,7 b' def branches(web, req):'
42 for b in web.repo.branches(nodes):
45 for b in web.repo.branches(nodes):
43 resp.write(" ".join(map(hex, b)) + "\n")
46 resp.write(" ".join(map(hex, b)) + "\n")
44 resp = resp.getvalue()
47 resp = resp.getvalue()
45 req.httphdr("application/mercurial-0.1", length=len(resp))
48 req.respond(HTTP_OK, HGTYPE, length=len(resp))
46 req.write(resp)
49 req.write(resp)
47
50
48 def between(web, req):
51 def between(web, req):
@@ -53,11 +56,11 b' def between(web, req):'
53 for b in web.repo.between(pairs):
56 for b in web.repo.between(pairs):
54 resp.write(" ".join(map(hex, b)) + "\n")
57 resp.write(" ".join(map(hex, b)) + "\n")
55 resp = resp.getvalue()
58 resp = resp.getvalue()
56 req.httphdr("application/mercurial-0.1", length=len(resp))
59 req.respond(HTTP_OK, HGTYPE, length=len(resp))
57 req.write(resp)
60 req.write(resp)
58
61
59 def changegroup(web, req):
62 def changegroup(web, req):
60 req.httphdr("application/mercurial-0.1")
63 req.respond(HTTP_OK, HGTYPE)
61 nodes = []
64 nodes = []
62 if not web.allowpull:
65 if not web.allowpull:
63 return
66 return
@@ -76,7 +79,7 b' def changegroup(web, req):'
76 req.write(z.flush())
79 req.write(z.flush())
77
80
78 def changegroupsubset(web, req):
81 def changegroupsubset(web, req):
79 req.httphdr("application/mercurial-0.1")
82 req.respond(HTTP_OK, HGTYPE)
80 bases = []
83 bases = []
81 heads = []
84 heads = []
82 if not web.allowpull:
85 if not web.allowpull:
@@ -106,7 +109,7 b' def capabilities(web, req):'
106 if unbundleversions:
109 if unbundleversions:
107 caps.append('unbundle=%s' % ','.join(unbundleversions))
110 caps.append('unbundle=%s' % ','.join(unbundleversions))
108 resp = ' '.join(caps)
111 resp = ' '.join(caps)
109 req.httphdr("application/mercurial-0.1", length=len(resp))
112 req.respond(HTTP_OK, HGTYPE, length=len(resp))
110 req.write(resp)
113 req.write(resp)
111
114
112 def unbundle(web, req):
115 def unbundle(web, req):
@@ -116,7 +119,8 b' def unbundle(web, req):'
116 # drain incoming bundle, else client will not see
119 # drain incoming bundle, else client will not see
117 # response when run outside cgi script
120 # response when run outside cgi script
118 pass
121 pass
119 req.httphdr("application/mercurial-0.1", headers=headers)
122 req.header(headers.items())
123 req.respond(HTTP_OK, HGTYPE)
120 req.write('0\n')
124 req.write('0\n')
121 req.write(response)
125 req.write(response)
122
126
@@ -148,7 +152,7 b' def unbundle(web, req):'
148 bail(_('unsynced changes\n'))
152 bail(_('unsynced changes\n'))
149 return
153 return
150
154
151 req.httphdr("application/mercurial-0.1")
155 req.respond(HTTP_OK, HGTYPE)
152
156
153 # do not lock repo until all changegroup data is
157 # do not lock repo until all changegroup data is
154 # streamed. save to temporary file.
158 # streamed. save to temporary file.
@@ -232,14 +236,15 b' def unbundle(web, req):'
232 filename = ''
236 filename = ''
233 error = getattr(inst, 'strerror', 'Unknown error')
237 error = getattr(inst, 'strerror', 'Unknown error')
234 if inst.errno == errno.ENOENT:
238 if inst.errno == errno.ENOENT:
235 code = 404
239 code = HTTP_NOT_FOUND
236 else:
240 else:
237 code = 500
241 code = HTTP_SERVER_ERROR
238 req.respond(code, '%s: %s\n' % (error, filename))
242 req.respond(code)
243 req.write('%s: %s\n' % (error, filename))
239 finally:
244 finally:
240 fp.close()
245 fp.close()
241 os.unlink(tempname)
246 os.unlink(tempname)
242
247
243 def stream_out(web, req):
248 def stream_out(web, req):
244 req.httphdr("application/mercurial-0.1")
249 req.respond(HTTP_OK, HGTYPE)
245 streamclone.stream_out(web.repo, req, untrusted=True)
250 streamclone.stream_out(web.repo, req, untrusted=True)
@@ -17,7 +17,6 b' class wsgirequest(object):'
17 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
17 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
18 % version)
18 % version)
19 self.inp = wsgienv['wsgi.input']
19 self.inp = wsgienv['wsgi.input']
20 self.server_write = None
21 self.err = wsgienv['wsgi.errors']
20 self.err = wsgienv['wsgi.errors']
22 self.threaded = wsgienv['wsgi.multithread']
21 self.threaded = wsgienv['wsgi.multithread']
23 self.multiprocess = wsgienv['wsgi.multiprocess']
22 self.multiprocess = wsgienv['wsgi.multiprocess']
@@ -25,6 +24,7 b' class wsgirequest(object):'
25 self.env = wsgienv
24 self.env = wsgienv
26 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
25 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
27 self._start_response = start_response
26 self._start_response = start_response
27 self.server_write = None
28 self.headers = []
28 self.headers = []
29
29
30 def __iter__(self):
30 def __iter__(self):
@@ -33,8 +33,10 b' class wsgirequest(object):'
33 def read(self, count=-1):
33 def read(self, count=-1):
34 return self.inp.read(count)
34 return self.inp.read(count)
35
35
36 def start_response(self, status):
36 def respond(self, status, type=None, filename=None, length=0):
37 if self._start_response is not None:
37 if self._start_response is not None:
38
39 self.httphdr(type, filename, length)
38 if not self.headers:
40 if not self.headers:
39 raise RuntimeError("request.write called before headers sent")
41 raise RuntimeError("request.write called before headers sent")
40
42
@@ -44,6 +46,8 b' class wsgirequest(object):'
44
46
45 if isinstance(status, ErrorResponse):
47 if isinstance(status, ErrorResponse):
46 status = statusmessage(status.code)
48 status = statusmessage(status.code)
49 elif status == 200:
50 status = '200 Script output follows'
47 elif isinstance(status, int):
51 elif isinstance(status, int):
48 status = statusmessage(status)
52 status = statusmessage(status)
49
53
@@ -51,24 +55,17 b' class wsgirequest(object):'
51 self._start_response = None
55 self._start_response = None
52 self.headers = []
56 self.headers = []
53
57
54 def respond(self, status, *things):
58 def write(self, thing):
55 if not things:
59 if hasattr(thing, "__iter__"):
56 self.start_response(status)
60 for part in thing:
57 for thing in things:
61 self.write(part)
58 if hasattr(thing, "__iter__"):
62 else:
59 for part in thing:
63 thing = str(thing)
60 self.respond(status, part)
64 try:
61 else:
65 self.server_write(thing)
62 thing = str(thing)
66 except socket.error, inst:
63 self.start_response(status)
67 if inst[0] != errno.ECONNRESET:
64 try:
68 raise
65 self.server_write(thing)
66 except socket.error, inst:
67 if inst[0] != errno.ECONNRESET:
68 raise
69
70 def write(self, *things):
71 self.respond('200 Script output follows', *things)
72
69
73 def writelines(self, lines):
70 def writelines(self, lines):
74 for line in lines:
71 for line in lines:
@@ -83,9 +80,10 b' class wsgirequest(object):'
83 def header(self, headers=[('Content-Type','text/html')]):
80 def header(self, headers=[('Content-Type','text/html')]):
84 self.headers.extend(headers)
81 self.headers.extend(headers)
85
82
86 def httphdr(self, type, filename=None, length=0, headers={}):
83 def httphdr(self, type=None, filename=None, length=0, headers={}):
87 headers = headers.items()
84 headers = headers.items()
88 headers.append(('Content-Type', type))
85 if type is not None:
86 headers.append(('Content-Type', type))
89 if filename:
87 if filename:
90 headers.append(('Content-Disposition', 'inline; filename=%s' %
88 headers.append(('Content-Disposition', 'inline; filename=%s' %
91 filename))
89 filename))
@@ -7,7 +7,7 b''
7
7
8 import os, mimetypes
8 import os, mimetypes
9 from mercurial import revlog, util, hg
9 from mercurial import revlog, util, hg
10 from common import staticfile, ErrorResponse
10 from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
11
11
12 # __all__ is populated with the allowed commands. Be sure to add to it if
12 # __all__ is populated with the allowed commands. Be sure to add to it if
13 # you're adding a new command, or the new command won't work.
13 # you're adding a new command, or the new command won't work.
@@ -27,12 +27,16 b' def log(web, req, tmpl):'
27 def rawfile(web, req, tmpl):
27 def rawfile(web, req, tmpl):
28 path = web.cleanpath(req.form.get('file', [''])[0])
28 path = web.cleanpath(req.form.get('file', [''])[0])
29 if not path:
29 if not path:
30 return web.manifest(tmpl, web.changectx(req), path)
30 content = web.manifest(tmpl, web.changectx(req), path)
31 req.respond(HTTP_OK, web.ctype)
32 return content
31
33
32 try:
34 try:
33 fctx = web.filectx(req)
35 fctx = web.filectx(req)
34 except revlog.LookupError:
36 except revlog.LookupError:
35 return web.manifest(tmpl, web.changectx(req), path)
37 content = web.manifest(tmpl, web.changectx(req), path)
38 req.respond(HTTP_OK, web.ctype)
39 return content
36
40
37 path = fctx.path()
41 path = fctx.path()
38 text = fctx.data()
42 text = fctx.data()
@@ -40,7 +44,7 b' def rawfile(web, req, tmpl):'
40 if mt is None or util.binary(text):
44 if mt is None or util.binary(text):
41 mt = mt or 'application/octet-stream'
45 mt = mt or 'application/octet-stream'
42
46
43 req.httphdr(mt, path, len(text))
47 req.respond(HTTP_OK, mt, path, len(text))
44 return [text]
48 return [text]
45
49
46 def file(web, req, tmpl):
50 def file(web, req, tmpl):
@@ -104,8 +108,7 b' def archive(web, req, tmpl):'
104 web.configbool("web", "allow" + type_, False))):
108 web.configbool("web", "allow" + type_, False))):
105 web.archive(tmpl, req, req.form['node'][0], type_)
109 web.archive(tmpl, req, req.form['node'][0], type_)
106 return []
110 return []
107
111 raise ErrorResponse(HTTP_NOT_FOUND, 'Unsupported archive type: %s' % type_)
108 raise ErrorResponse(400, 'Unsupported archive type: %s' % type_)
109
112
110 def static(web, req, tmpl):
113 def static(web, req, tmpl):
111 fname = req.form['file'][0]
114 fname = req.form['file'][0]
General Comments 0
You need to be logged in to leave comments. Login now