Mercurial > hg-stable
changeset 37016:b6a7070e7663
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
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Tue, 13 Mar 2018 11:17:10 -0700 |
parents | fc8939825632 |
children | a5311d7f4af8 |
files | mercurial/debugcommands.py tests/test-http-protocol.t |
diffstat | 2 files changed, 91 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- 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