# HG changeset patch # User Gregory Szorc # Date 2018-03-11 01:02:57 # Node ID 1f42d621f090b2ad88e747557c0504212c524fda # Parent 7ad6a275316f9ba5ec50b09bd1470f2bc8f7c084 hgweb: support using new response object for web commands We have a "requestcontext" type for holding state for the current request. Why we pass in the wsgirequest and templater instance to @webcommand functions, I don't know. I like the idea of standardizing on using "requestcontext" for passing all state to @webcommand functions because that scales well without API changes every time you want to pass a new piece of data. So, we add our new request and response instances to "requestcontext" so @webcommand functions can access them. We also teach our command dispatcher to recognize a new calling convention. Instead of returning content from the @webcommand function, we return our response object. This signals that this response object is to be used for sending output. The keyword extension was wrapping various @webcommand and assuming the output was iterable, so we had to teach it about the new calling convention. To prove everything works, we convert the "filelog" @webcommand to use the new convention. The new calling convention is a bit wonky. I intend to improve this once all commands are ported to use the new response object. Differential Revision: https://phab.mercurial-scm.org/D2786 diff --git a/hgext/keyword.py b/hgext/keyword.py --- a/hgext/keyword.py +++ b/hgext/keyword.py @@ -621,7 +621,10 @@ def kwweb_skip(orig, web, req, tmpl): origmatch = kwt.match kwt.match = util.never try: - for chunk in orig(web, req, tmpl): + res = orig(web, req, tmpl) + if res is web.res: + res = res.sendresponse() + for chunk in res: yield chunk finally: if kwt: 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 @@ -91,9 +91,11 @@ class requestcontext(object): is prone to race conditions. Instances of this class exist to hold mutable and race-free state for requests. """ - def __init__(self, app, repo): + def __init__(self, app, repo, req, res): self.repo = repo self.reponame = app.reponame + self.req = req + self.res = res self.archivespecs = archivespecs @@ -305,7 +307,7 @@ class hgweb(object): def _runwsgi(self, wsgireq, repo): req = wsgireq.req res = wsgireq.res - rctx = requestcontext(self, repo) + rctx = requestcontext(self, repo, req, res) # This state is global across all threads. encoding.encoding = rctx.config('web', 'encoding') @@ -401,7 +403,15 @@ class hgweb(object): rctx.ctype = ctype content = webcommands.rawfile(rctx, wsgireq, tmpl) else: + # Set some globals appropriate for web handlers. Commands can + # override easily enough. + res.status = '200 Script output follows' + res.headers['Content-Type'] = ctype content = getattr(webcommands, cmd)(rctx, wsgireq, tmpl) + + if content is res: + return res.sendresponse() + wsgireq.respond(HTTP_OK, ctype) return content diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py +++ b/mercurial/hgweb/webcommands.py @@ -53,6 +53,16 @@ class webcommand(object): The decorator takes as its positional arguments the name/path the command should be accessible under. + When called, functions receive as arguments a ``requestcontext``, + ``wsgirequest``, and a templater instance for generatoring output. + The functions should populate the ``rctx.res`` object with details + about the HTTP response. + + The function can return the ``requestcontext.res`` instance to signal + that it wants to use this object to generate the response. If an iterable + is returned, the ``wsgirequest`` instance will be used and the returned + content will constitute the response body. + Usage: @webcommand('mycommand') @@ -1068,19 +1078,22 @@ def filelog(web, req, tmpl): latestentry = entries[:1] - return tmpl("filelog", - file=f, - nav=nav, - symrev=webutil.symrevorshortnode(req, fctx), - entries=entries, - descend=descend, - patch=patch, - latestentry=latestentry, - linerange=linerange, - revcount=revcount, - morevars=morevars, - lessvars=lessvars, - **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))) + web.res.setbodygen(tmpl( + 'filelog', + file=f, + nav=nav, + symrev=webutil.symrevorshortnode(req, fctx), + entries=entries, + descend=descend, + patch=patch, + latestentry=latestentry, + linerange=linerange, + revcount=revcount, + morevars=morevars, + lessvars=lessvars, + **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)))) + + return web.res @webcommand('archive') def archive(web, req, tmpl):