debugcommands: support sending HTTP requests with debugwireproto
authorGregory Szorc <gregory.szorc@gmail.com>
Tue, 13 Mar 2018 11:17:10 -0700
changeset 37013 b6a7070e7663
parent 37012 fc8939825632
child 37014 a5311d7f4af8
debugcommands: support sending HTTP requests with debugwireproto We implement an action that can issue an HTTP request. We can define headers via arguments and specify a file to use for the HTTP request body. The request uses the HTTP peer's opener, which is already configured for auth, etc. This is both good and bad. Good in that we get some nice behavior out of the box. Bad in that some HTTP request headers are added automatically. Differential Revision: https://phab.mercurial-scm.org/D2841
mercurial/debugcommands.py
tests/test-http-protocol.t
--- a/mercurial/debugcommands.py	Tue Mar 13 10:02:03 2018 -0700
+++ b/mercurial/debugcommands.py	Tue Mar 13 11:17:10 2018 -0700
@@ -14,6 +14,7 @@
 import operator
 import os
 import random
+import re
 import socket
 import ssl
 import stat
@@ -2692,6 +2693,24 @@
 
     This action MUST be paired with a ``batchbegin`` action.
 
+    httprequest <method> <path>
+    ---------------------------
+
+    (HTTP peer only)
+
+    Send an HTTP request to the peer.
+
+    The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
+
+    Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
+    headers to add to the request. e.g. ``Accept: foo``.
+
+    The following arguments are special:
+
+    ``BODYFILE``
+        The content of the file defined as the value to this argument will be
+        transferred verbatim as the HTTP request body.
+
     close
     -----
 
@@ -2754,6 +2773,7 @@
     stdin = None
     stdout = None
     stderr = None
+    opener = None
 
     if opts['localssh']:
         # We start the SSH server in its own process so there is process
@@ -2909,6 +2929,42 @@
                 ui.status(_('response #%d: %s\n') % (i, util.escapedata(chunk)))
 
             batchedcommands = None
+
+        elif action.startswith('httprequest '):
+            if not opener:
+                raise error.Abort(_('cannot use httprequest without an HTTP '
+                                    'peer'))
+
+            request = action.split(' ', 2)
+            if len(request) != 3:
+                raise error.Abort(_('invalid httprequest: expected format is '
+                                    '"httprequest <method> <path>'))
+
+            method, httppath = request[1:]
+            headers = {}
+            body = None
+            for line in lines:
+                line = line.lstrip()
+                m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
+                if m:
+                    headers[m.group(1)] = m.group(2)
+                    continue
+
+                if line.startswith(b'BODYFILE '):
+                    with open(line.split(b' ', 1), 'rb') as fh:
+                        body = fh.read()
+                else:
+                    raise error.Abort(_('unknown argument to httprequest: %s') %
+                                      line)
+
+            url = path + httppath
+            req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
+
+            try:
+                opener.open(req).read()
+            except util.urlerr.urlerror as e:
+                e.read()
+
         elif action == 'close':
             peer.close()
         elif action == 'readavailable':
--- a/tests/test-http-protocol.t	Tue Mar 13 10:02:03 2018 -0700
+++ b/tests/test-http-protocol.t	Tue Mar 13 11:17:10 2018 -0700
@@ -226,4 +226,39 @@
   s>     phases	
   response: bookmarks	\nnamespaces	\nphases	
 
+Same thing, but with "httprequest" command
+
+  $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
+  > httprequest GET ?cmd=listkeys
+  >     accept: application/mercurial-0.1
+  >     user-agent: mercurial/proto-1.0 (Mercurial 42)
+  >     x-hgarg-1: namespace=namespaces
+  > EOF
+  using raw connection to peer
+  s> sendall(*, 0): (glob)
+  s>     GET /?cmd=listkeys HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-0.1\r\n
+  s>     user-agent: mercurial/proto-1.0 (Mercurial 42)\r\n (glob)
+  s>     x-hgarg-1: namespace=namespaces\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     \r\n
+  s> makefile('rb', None)
+  s> readline() -> 36:
+  s>     HTTP/1.1 200 Script output follows\r\n
+  s> readline() -> 28:
+  s>     Server: testing stub value\r\n
+  s> readline() -> *: (glob)
+  s>     Date: $HTTP_DATE$\r\n
+  s> readline() -> 41:
+  s>     Content-Type: application/mercurial-0.1\r\n
+  s> readline() -> 20:
+  s>     Content-Length: 30\r\n
+  s> readline() -> 2:
+  s>     \r\n
+  s> read(30) -> 30:
+  s>     bookmarks	\n
+  s>     namespaces	\n
+  s>     phases	
+
   $ killdaemons.py