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
--- a/hgext/keyword.py Sat Mar 10 14:19:27 2018 -0800
+++ b/hgext/keyword.py Sat Mar 10 17:02:57 2018 -0800
@@ -621,7 +621,10 @@
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:
--- a/mercurial/hgweb/hgweb_mod.py Sat Mar 10 14:19:27 2018 -0800
+++ b/mercurial/hgweb/hgweb_mod.py Sat Mar 10 17:02:57 2018 -0800
@@ -91,9 +91,11 @@
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 @@
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 @@
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
--- a/mercurial/hgweb/webcommands.py Sat Mar 10 14:19:27 2018 -0800
+++ b/mercurial/hgweb/webcommands.py Sat Mar 10 17:02:57 2018 -0800
@@ -53,6 +53,16 @@
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 @@
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):