# HG changeset patch # User Dirkjan Ochtman # Date 2008-02-01 09:31:13 # Node ID 948a41e77902313de6d58e6cda8152614604b148 # Parent 30c40ba109630d0ece461e1976228d872cbb6a92 hgweb: explicit response status diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py --- a/mercurial/hgweb/common.py +++ b/mercurial/hgweb/common.py @@ -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. diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -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 diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -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): diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py --- a/mercurial/hgweb/protocol.py +++ b/mercurial/hgweb/protocol.py @@ -9,6 +9,7 @@ import cStringIO, zlib, bz2, tempfile, e 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 @@ from mercurial.node import * '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) diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -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)) diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py +++ b/mercurial/hgweb/webcommands.py @@ -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]