Patchwork D2786: hgweb: support using new response object for web commands

login
register
mail settings
Submitter phabricator
Date March 11, 2018, 5:24 a.m.
Message ID <differential-rev-PHID-DREV-7ih4yehajg2lpbxt5pif-req@phab.mercurial-scm.org>
Download mbox | patch
Permalink /patch/29282/
State Superseded
Headers show

Comments

phabricator - March 11, 2018, 5:24 a.m.
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  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.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2786

AFFECTED FILES
  hgext/keyword.py
  mercurial/hgweb/hgweb_mod.py
  mercurial/hgweb/webcommands.py

CHANGE DETAILS




To: indygreg, #hg-reviewers
Cc: mercurial-devel

Patch

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 @@ 
     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):
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 @@ 
     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
diff --git a/hgext/keyword.py b/hgext/keyword.py
--- a/hgext/keyword.py
+++ b/hgext/keyword.py
@@ -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: