hgweb: explicit response status
authorDirkjan Ochtman <dirkjan@ochtman.nl>
Fri, 01 Feb 2008 10:31:13 +0100
changeset 5993 948a41e77902
parent 5992 30c40ba10963
child 5994 a445388aa554
hgweb: explicit response status
mercurial/hgweb/common.py
mercurial/hgweb/hgweb_mod.py
mercurial/hgweb/hgwebdir_mod.py
mercurial/hgweb/protocol.py
mercurial/hgweb/request.py
mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/common.py	Fri Feb 01 10:31:09 2008 +0100
+++ b/mercurial/hgweb/common.py	Fri Feb 01 10:31:13 2008 +0100
@@ -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 @@
     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.
--- a/mercurial/hgweb/hgweb_mod.py	Fri Feb 01 10:31:09 2008 +0100
+++ b/mercurial/hgweb/hgweb_mod.py	Fri Feb 01 10:31:13 2008 +0100
@@ -11,6 +11,7 @@
 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 @@
                 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 @@
         # 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 @@
                 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 @@
         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
--- a/mercurial/hgweb/hgwebdir_mod.py	Fri Feb 01 10:31:09 2008 +0100
+++ b/mercurial/hgweb/hgwebdir_mod.py	Fri Feb 01 10:31:13 2008 +0100
@@ -10,7 +10,7 @@
 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 @@
             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 @@
 
                 # 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 @@
                             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 @@
                     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 @@
     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):
--- a/mercurial/hgweb/protocol.py	Fri Feb 01 10:31:09 2008 +0100
+++ b/mercurial/hgweb/protocol.py	Fri Feb 01 10:31:13 2008 +0100
@@ -9,6 +9,7 @@
 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 @@
    '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 @@
         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 @@
     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 @@
     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 @@
     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 @@
     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 @@
             # 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 @@
         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 @@
                 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)
--- a/mercurial/hgweb/request.py	Fri Feb 01 10:31:09 2008 +0100
+++ b/mercurial/hgweb/request.py	Fri Feb 01 10:31:13 2008 +0100
@@ -17,7 +17,6 @@
             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 @@
         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 @@
     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 @@
 
             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 @@
             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 @@
     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))
--- a/mercurial/hgweb/webcommands.py	Fri Feb 01 10:31:09 2008 +0100
+++ b/mercurial/hgweb/webcommands.py	Fri Feb 01 10:31:13 2008 +0100
@@ -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 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 @@
     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 @@
         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]