hgweb: explicit response status

This commit is contained in:
Dirkjan Ochtman 2008-02-01 10:31:13 +01:00
parent ac2a5adcee
commit 3d668210f2
6 changed files with 86 additions and 67 deletions

View File

@ -8,6 +8,11 @@
import errno, mimetypes, os
HTTP_OK = 200
HTTP_BAD_REQUEST = 400
HTTP_NOT_FOUND = 404
HTTP_SERVER_ERROR = 500
class ErrorResponse(Exception):
def __init__(self, code, message=None):
Exception.__init__(self)
@ -54,18 +59,15 @@ def staticfile(directory, fname, req):
try:
os.stat(path)
ct = mimetypes.guess_type(path)[0] or "text/plain"
req.header([
('Content-Type', ct),
('Content-Length', str(os.path.getsize(path)))
])
req.respond(HTTP_OK, ct, length = os.path.getsize(path))
return file(path, 'rb').read()
except TypeError:
raise ErrorResponse(500, 'illegal file name')
raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal file name')
except OSError, err:
if err.errno == errno.ENOENT:
raise ErrorResponse(404)
raise ErrorResponse(HTTP_NOT_FOUND)
else:
raise ErrorResponse(500, err.strerror)
raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
def style_map(templatepath, style):
"""Return path to mapfile for a given style.

View File

@ -11,6 +11,7 @@ from mercurial.node import *
from mercurial import mdiff, ui, hg, util, archival, patch, hook
from mercurial import revlog, templater, templatefilters
from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact
from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
from request import wsgirequest
import webcommands, protocol
@ -207,27 +208,35 @@ class hgweb(object):
method(self, req)
else:
tmpl = self.templater(req)
ctype = tmpl('mimetype', encoding=self.encoding)
ctype = templater.stringify(ctype)
if cmd == '':
req.form['cmd'] = [tmpl.cache['default']]
cmd = req.form['cmd'][0]
if cmd not in webcommands.__all__:
raise ErrorResponse(400, 'No such method: ' + cmd)
msg = 'No such method: %s' % cmd
raise ErrorResponse(HTTP_BAD_REQUEST, msg)
elif cmd == 'file' and 'raw' in req.form.get('style', []):
self.ctype = ctype
content = webcommands.rawfile(self, req, tmpl)
else:
content = getattr(webcommands, cmd)(self, req, tmpl)
req.respond(HTTP_OK, ctype)
req.write(content)
del tmpl
except revlog.LookupError, err:
req.respond(404, tmpl(
'error', error='revision not found: %s' % err.name))
req.respond(HTTP_NOT_FOUND, ctype)
req.write(tmpl('error', error='revision not found: %s' % err.name))
except (hg.RepoError, revlog.RevlogError), inst:
req.respond(500, tmpl('error', error=str(inst)))
req.respond(HTTP_SERVER_ERROR, ctype)
req.write(tmpl('error', error=str(inst)))
except ErrorResponse, inst:
req.respond(inst.code, tmpl('error', error=inst.message))
req.respond(inst.code, ctype)
req.write(tmpl('error', error=inst.message))
def templater(self, req):
@ -252,8 +261,6 @@ class hgweb(object):
# some functions for the templater
def header(**map):
ctype = tmpl('mimetype', encoding=self.encoding)
req.httphdr(templater.stringify(ctype))
yield tmpl('header', encoding=self.encoding, **map)
def footer(**map):
@ -668,7 +675,7 @@ class hgweb(object):
files[short] = (f, n)
if not files:
raise ErrorResponse(404, 'Path not found: ' + path)
raise ErrorResponse(HTTP_NOT_FOUND, 'Path not found: ' + path)
def filelist(**map):
fl = files.keys()
@ -846,6 +853,7 @@ class hgweb(object):
if encoding:
headers.append(('Content-Encoding', encoding))
req.header(headers)
req.respond(HTTP_OK)
archival.archive(self.repo, req, cnode, artype, prefix=name)
# add tags to things

View File

@ -10,7 +10,7 @@ import os
from mercurial.i18n import gettext as _
from mercurial import ui, hg, util, templater, templatefilters
from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
get_contact
get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
from hgweb_mod import hgweb
from request import wsgirequest
@ -76,6 +76,9 @@ class hgwebdir(object):
try:
virtual = req.env.get("PATH_INFO", "").strip('/')
tmpl = self.templater(req)
ctype = tmpl('mimetype', encoding=util._encoding)
ctype = templater.stringify(ctype)
# a static file
if virtual.startswith('static/') or 'static' in req.form:
@ -89,11 +92,12 @@ class hgwebdir(object):
# top-level index
elif not virtual:
tmpl = self.templater(req)
req.respond(HTTP_OK, ctype)
req.write(self.makeindex(req, tmpl))
return
# nested indexes and hgwebs
repos = dict(self.repos)
while virtual:
real = repos.get(virtual)
@ -104,14 +108,15 @@ class hgwebdir(object):
hgweb(repo).run_wsgi(req)
return
except IOError, inst:
raise ErrorResponse(500, inst.strerror)
msg = inst.strerror
raise ErrorResponse(HTTP_SERVER_ERROR, msg)
except hg.RepoError, inst:
raise ErrorResponse(500, str(inst))
raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
# browse subdirectories
subdir = virtual + '/'
if [r for r in repos if r.startswith(subdir)]:
tmpl = self.templater(req)
req.respond(HTTP_OK, ctype)
req.write(self.makeindex(req, tmpl, subdir))
return
@ -121,12 +126,12 @@ class hgwebdir(object):
virtual = virtual[:up]
# prefixes not found
tmpl = self.templater(req)
req.respond(404, tmpl("notfound", repo=virtual))
req.respond(HTTP_NOT_FOUND, ctype)
req.write(tmpl("notfound", repo=virtual))
except ErrorResponse, err:
tmpl = self.templater(req)
req.respond(err.code, tmpl('error', error=err.message or ''))
req.respond(err.code, ctype)
req.write(tmpl('error', error=err.message or ''))
finally:
tmpl = None
@ -234,8 +239,6 @@ class hgwebdir(object):
def templater(self, req):
def header(**map):
ctype = tmpl('mimetype', encoding=util._encoding)
req.httphdr(templater.stringify(ctype))
yield tmpl('header', encoding=util._encoding, **map)
def footer(**map):

View File

@ -9,6 +9,7 @@ import cStringIO, zlib, bz2, tempfile, errno, os, sys
from mercurial import util, streamclone
from mercurial.i18n import gettext as _
from mercurial.node import *
from common import HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
# __all__ is populated with the allowed commands. Be sure to add to it if
# you're adding a new command, or the new command won't work.
@ -18,6 +19,8 @@ __all__ = [
'changegroupsubset', 'capabilities', 'unbundle', 'stream_out',
]
HGTYPE = 'application/mercurial-0.1'
def lookup(web, req):
try:
r = hex(web.repo.lookup(req.form['key'][0]))
@ -26,12 +29,12 @@ def lookup(web, req):
r = str(inst)
success = 0
resp = "%s %s\n" % (success, r)
req.httphdr("application/mercurial-0.1", length=len(resp))
req.respond(HTTP_OK, HGTYPE, length=len(resp))
req.write(resp)
def heads(web, req):
resp = " ".join(map(hex, web.repo.heads())) + "\n"
req.httphdr("application/mercurial-0.1", length=len(resp))
req.respond(HTTP_OK, HGTYPE, length=len(resp))
req.write(resp)
def branches(web, req):
@ -42,7 +45,7 @@ def branches(web, req):
for b in web.repo.branches(nodes):
resp.write(" ".join(map(hex, b)) + "\n")
resp = resp.getvalue()
req.httphdr("application/mercurial-0.1", length=len(resp))
req.respond(HTTP_OK, HGTYPE, length=len(resp))
req.write(resp)
def between(web, req):
@ -53,11 +56,11 @@ def between(web, req):
for b in web.repo.between(pairs):
resp.write(" ".join(map(hex, b)) + "\n")
resp = resp.getvalue()
req.httphdr("application/mercurial-0.1", length=len(resp))
req.respond(HTTP_OK, HGTYPE, length=len(resp))
req.write(resp)
def changegroup(web, req):
req.httphdr("application/mercurial-0.1")
req.respond(HTTP_OK, HGTYPE)
nodes = []
if not web.allowpull:
return
@ -76,7 +79,7 @@ def changegroup(web, req):
req.write(z.flush())
def changegroupsubset(web, req):
req.httphdr("application/mercurial-0.1")
req.respond(HTTP_OK, HGTYPE)
bases = []
heads = []
if not web.allowpull:
@ -106,7 +109,7 @@ def capabilities(web, req):
if unbundleversions:
caps.append('unbundle=%s' % ','.join(unbundleversions))
resp = ' '.join(caps)
req.httphdr("application/mercurial-0.1", length=len(resp))
req.respond(HTTP_OK, HGTYPE, length=len(resp))
req.write(resp)
def unbundle(web, req):
@ -116,7 +119,8 @@ def unbundle(web, req):
# drain incoming bundle, else client will not see
# response when run outside cgi script
pass
req.httphdr("application/mercurial-0.1", headers=headers)
req.header(headers.items())
req.respond(HTTP_OK, HGTYPE)
req.write('0\n')
req.write(response)
@ -148,7 +152,7 @@ def unbundle(web, req):
bail(_('unsynced changes\n'))
return
req.httphdr("application/mercurial-0.1")
req.respond(HTTP_OK, HGTYPE)
# do not lock repo until all changegroup data is
# streamed. save to temporary file.
@ -232,14 +236,15 @@ def unbundle(web, req):
filename = ''
error = getattr(inst, 'strerror', 'Unknown error')
if inst.errno == errno.ENOENT:
code = 404
code = HTTP_NOT_FOUND
else:
code = 500
req.respond(code, '%s: %s\n' % (error, filename))
code = HTTP_SERVER_ERROR
req.respond(code)
req.write('%s: %s\n' % (error, filename))
finally:
fp.close()
os.unlink(tempname)
def stream_out(web, req):
req.httphdr("application/mercurial-0.1")
req.respond(HTTP_OK, HGTYPE)
streamclone.stream_out(web.repo, req, untrusted=True)

View File

@ -17,7 +17,6 @@ class wsgirequest(object):
raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
% version)
self.inp = wsgienv['wsgi.input']
self.server_write = None
self.err = wsgienv['wsgi.errors']
self.threaded = wsgienv['wsgi.multithread']
self.multiprocess = wsgienv['wsgi.multiprocess']
@ -25,6 +24,7 @@ class wsgirequest(object):
self.env = wsgienv
self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
self._start_response = start_response
self.server_write = None
self.headers = []
def __iter__(self):
@ -33,8 +33,10 @@ class wsgirequest(object):
def read(self, count=-1):
return self.inp.read(count)
def start_response(self, status):
def respond(self, status, type=None, filename=None, length=0):
if self._start_response is not None:
self.httphdr(type, filename, length)
if not self.headers:
raise RuntimeError("request.write called before headers sent")
@ -44,6 +46,8 @@ class wsgirequest(object):
if isinstance(status, ErrorResponse):
status = statusmessage(status.code)
elif status == 200:
status = '200 Script output follows'
elif isinstance(status, int):
status = statusmessage(status)
@ -51,24 +55,17 @@ class wsgirequest(object):
self._start_response = None
self.headers = []
def respond(self, status, *things):
if not things:
self.start_response(status)
for thing in things:
if hasattr(thing, "__iter__"):
for part in thing:
self.respond(status, part)
else:
thing = str(thing)
self.start_response(status)
try:
self.server_write(thing)
except socket.error, inst:
if inst[0] != errno.ECONNRESET:
raise
def write(self, *things):
self.respond('200 Script output follows', *things)
def write(self, thing):
if hasattr(thing, "__iter__"):
for part in thing:
self.write(part)
else:
thing = str(thing)
try:
self.server_write(thing)
except socket.error, inst:
if inst[0] != errno.ECONNRESET:
raise
def writelines(self, lines):
for line in lines:
@ -83,9 +80,10 @@ class wsgirequest(object):
def header(self, headers=[('Content-Type','text/html')]):
self.headers.extend(headers)
def httphdr(self, type, filename=None, length=0, headers={}):
def httphdr(self, type=None, filename=None, length=0, headers={}):
headers = headers.items()
headers.append(('Content-Type', type))
if type is not None:
headers.append(('Content-Type', type))
if filename:
headers.append(('Content-Disposition', 'inline; filename=%s' %
filename))

View File

@ -7,7 +7,7 @@
import os, mimetypes
from mercurial import revlog, util, hg
from common import staticfile, ErrorResponse
from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
# __all__ is populated with the allowed commands. Be sure to add to it if
# you're adding a new command, or the new command won't work.
@ -27,12 +27,16 @@ def log(web, req, tmpl):
def rawfile(web, req, tmpl):
path = web.cleanpath(req.form.get('file', [''])[0])
if not path:
return web.manifest(tmpl, web.changectx(req), path)
content = web.manifest(tmpl, web.changectx(req), path)
req.respond(HTTP_OK, web.ctype)
return content
try:
fctx = web.filectx(req)
except revlog.LookupError:
return web.manifest(tmpl, web.changectx(req), path)
content = web.manifest(tmpl, web.changectx(req), path)
req.respond(HTTP_OK, web.ctype)
return content
path = fctx.path()
text = fctx.data()
@ -40,7 +44,7 @@ def rawfile(web, req, tmpl):
if mt is None or util.binary(text):
mt = mt or 'application/octet-stream'
req.httphdr(mt, path, len(text))
req.respond(HTTP_OK, mt, path, len(text))
return [text]
def file(web, req, tmpl):
@ -104,8 +108,7 @@ def archive(web, req, tmpl):
web.configbool("web", "allow" + type_, False))):
web.archive(tmpl, req, req.form['node'][0], type_)
return []
raise ErrorResponse(400, 'Unsupported archive type: %s' % type_)
raise ErrorResponse(HTTP_NOT_FOUND, 'Unsupported archive type: %s' % type_)
def static(web, req, tmpl):
fname = req.form['file'][0]