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
--- 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