mirror of
https://github.com/facebook/sapling.git
synced 2024-10-09 00:14:35 +03:00
hgweb: explicit response status
This commit is contained in:
parent
ac2a5adcee
commit
3d668210f2
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user