Patchwork D2019: wireprotoserver: move protocol parsing and dispatch out of hgweb

mail settings
Submitter phabricator
Date Feb. 3, 2018, 1:50 a.m.
Message ID <>
Download mbox | patch
Permalink /patch/27249/
State Superseded
Headers show


phabricator - Feb. 3, 2018, 1:50 a.m.
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

  Previously, hgweb_mod had code for detecting if the request
  was for the wire protocol. It would then (eventually) call
  wireprotoserver.callhttp() to dispatch the request handling.
  Detection of wire protocol requests is not trivial. There's
  currently a big gotcha in the handling of the "cmd" request
  parameter, for example.
  Furthermore, in the near future we will have a second HTTP
  protocol handler. Its mechanism for calling commands will be
  a bit different. And we don't want the low-level logic for
  detecting protocol commands to live in hgweb.
  We establish a new function in wireprotoserver for detecting
  an HTTP protocol request and for giving the caller an easy-to-use
  mechanism for dispatching requests to it.
  Some wire protocol specific functionality still lives in hgweb.
  This will be addressed in subsequent commits.

  rHG Mercurial




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


diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -205,9 +205,43 @@ 
 def iscmd(cmd):
     return cmd in wireproto.commands
-def callhttp(repo, req, cmd):
+def parsehttprequest(repo, req, query):
+    """Parse the HTTP request for a wire protocol request.
+    If the current request appears to be a wire protocol request, this
+    function returns a dict with details about that request, including
+    an ``abstractprotocolserver`` instance suitable for handling the
+    request. Otherwise, ``None`` is returned.
+    ``req`` is a ``wsgirequest`` instance.
+    """
+    # HTTP version 1 wire protocol requests are denoted by a "cmd" query
+    # string parameter. If it isn't present, this isn't a wire protocol
+    # request.
+    if r'cmd' not in req.form:
+        return None
+    cmd = pycompat.sysbytes(req.form[r'cmd'][0])
+    # The "cmd" request parameter is used by both the wire protocol and hgweb.
+    # While not all wire protocol commands are available for all transports,
+    # if we see a "cmd" value that resembles a known wire protocol command, we
+    # route it to a protocol handler. This is better than routing possible
+    # wire protocol requests to hgweb because it prevents hgweb from using
+    # known wire protocol commands and it is less confusing for machine
+    # clients.
+    if cmd not in wireproto.commands:
+        return None
     proto = webproto(req, repo.ui)
+    return {
+        'cmd': cmd,
+        'proto': proto,
+        'dispatch': lambda: _callhttp(repo, req, proto, cmd),
+    }
+def _callhttp(repo, req, proto, cmd):
     def genversion2(gen, engine, engineopts):
         # application/mercurial-0.2 always sends a payload header
         # identifying the compression engine.
diff --git a/mercurial/hgweb/ b/mercurial/hgweb/
--- a/mercurial/hgweb/
+++ b/mercurial/hgweb/
@@ -357,26 +357,18 @@ 
             query = req.env[r'QUERY_STRING'].partition(r'&')[0]
             query = query.partition(r';')[0]
-        # The ``cmd`` request parameter is used by both the wire protocol
-        # and hgweb. We route all known wire protocol commands to the
-        # wire protocol handler, even if the command isn't available for
-        # this transport. That's better for machine clients in the case
-        # of an errant request to an unavailable protocol command. And it
-        # prevents hgweb from accidentally using ``cmd`` values used by
-        # the wire protocol.
+        # Route it to a wire protocol handler if it looks like a wire protocol
+        # request.
+        protohandler = wireprotoserver.parsehttprequest(rctx.repo, req, query)
-        # process this if it's a protocol request
-        # protocol bits don't need to create any URLs
-        # and the clients always use the old URL structure
-        cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0])
-        if wireprotoserver.iscmd(cmd):
+        if protohandler:
+            cmd = protohandler['cmd']
                 if query:
                     raise ErrorResponse(HTTP_NOT_FOUND)
                 if cmd in perms:
                     self.check_perm(rctx, req, perms[cmd])
-                return wireprotoserver.callhttp(rctx.repo, req, cmd)
+                return protohandler['dispatch']()
             except ErrorResponse as inst:
                 # A client that sends unbundle without 100-continue will
                 # break if we respond early.
@@ -425,6 +417,8 @@ 
                     if fn.endswith(ext):
                         req.form['node'] = [fn[:-len(ext)]]
                         req.form['type'] = [type_]
+        else:
+            cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0])
         # process the web interface request