Patchwork D2841: debugcommands: support sending HTTP requests with debugwireproto

login
register
mail settings
Submitter phabricator
Date March 13, 2018, 7:12 p.m.
Message ID <differential-rev-PHID-DREV-t45gvm6uarqdwepepmex-req@phab.mercurial-scm.org>
Download mbox | patch
Permalink /patch/29453/
State Superseded
Headers show

Comments

phabricator - March 13, 2018, 7:12 p.m.
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

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

REPOSITORY
  rHG Mercurial

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

AFFECTED FILES
  mercurial/debugcommands.py
  tests/test-http-protocol.t

CHANGE DETAILS




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

Patch

diff --git a/tests/test-http-protocol.t b/tests/test-http-protocol.t
--- a/tests/test-http-protocol.t
+++ b/tests/test-http-protocol.t
@@ -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
diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -14,6 +14,7 @@ 
 import operator
 import os
 import random
+import re
 import socket
 import ssl
 import stat
@@ -2678,6 +2679,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
     -----
 
@@ -2740,6 +2759,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
@@ -2895,6 +2915,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':