##// END OF EJS Templates
changegroup: add v4 changegroup for revlog v2 exchange...
changegroup: add v4 changegroup for revlog v2 exchange This change only adds the required infrastructure for the new changegroup format and does not do any actual exchange. This will be done in the next patches. Differential Revision: https://phab.mercurial-scm.org/D10026

File last commit:

r47209:05dd091d default
r47445:a41565be default
Show More
httppeer.py
1121 lines | 34.1 KiB | text/x-python | PythonLexer
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 # httppeer.py - HTTP repository proxy classes for mercurial
#
# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
Gregory Szorc
httppeer: use absolute_import
r25954 from __future__ import absolute_import
import errno
Augie Fackler
httppeer: add support for httppostargs when we're sending a file...
r33820 import io
Gregory Szorc
httppeer: use absolute_import
r25954 import os
import socket
Gregory Szorc
httppeer: advertise and support application/mercurial-0.2...
r30763 import struct
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 import weakref
Gregory Szorc
httppeer: use absolute_import
r25954
from .i18n import _
Gregory Szorc
py3: manually import getattr where it is needed...
r43359 from .pycompat import getattr
Gregory Szorc
httppeer: use absolute_import
r25954 from . import (
Martin von Zweigbergk
bundle: move writebundle() from changegroup.py to bundle2.py (API)...
r28666 bundle2,
Gregory Szorc
httppeer: use absolute_import
r25954 error,
httpconnection,
Pulkit Goyal
py3: convert the mode argument of os.fdopen to unicodes (1 of 2)...
r30924 pycompat,
Gregory Szorc
httppeer: use absolute_import
r25954 statichttprepo,
Gregory Szorc
httppeer: alias url as urlmod...
r36977 url as urlmod,
Gregory Szorc
httppeer: use absolute_import
r25954 util,
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501 wireprotoframing,
Gregory Szorc
httppeer: support protocol upgrade...
r37576 wireprototypes,
Gregory Szorc
wireproto: move version 1 peer functionality to standalone module (API)...
r37632 wireprotov1peer,
Gregory Szorc
wireprotov2: move response handling out of httppeer...
r37737 wireprotov2peer,
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 wireprotov2server,
Gregory Szorc
httppeer: use absolute_import
r25954 )
Pulkit Goyal
interfaceutil: move to interfaces/...
r43079 from .interfaces import (
repository,
util as interfaceutil,
)
Gregory Szorc
interfaceutil: module to stub out zope.interface...
r37828 from .utils import (
Gregory Szorc
httppeer: use our CBOR decoder...
r39476 cborutil,
Gregory Szorc
httppeer: log commands for version 2 peer...
r39467 stringutil,
Gregory Szorc
interfaceutil: module to stub out zope.interface...
r37828 )
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Pulkit Goyal
py3: conditionalize httplib import...
r29455 httplib = util.httplib
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 urlerr = util.urlerr
urlreq = util.urlreq
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
httppeer: extract code for HTTP header spanning...
r30759 def encodevalueinheaders(value, header, limit):
"""Encode a string value into multiple HTTP headers.
``value`` will be encoded into 1 or more HTTP headers with the names
``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
name + value will be at most ``limit`` bytes long.
Augie Fackler
httppeer: always produce native str header keys and values...
r34733 Returns an iterable of 2-tuples consisting of header names and
values as native strings.
Gregory Szorc
httppeer: extract code for HTTP header spanning...
r30759 """
Augie Fackler
httppeer: always produce native str header keys and values...
r34733 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
# not bytes. This function always takes bytes in as arguments.
fmt = pycompat.strurl(header) + r'-%s'
# Note: it is *NOT* a bug that the last bit here is a bytestring
# and not a unicode: we're just getting the encoded length anyway,
# and using an r-string to make it portable between Python 2 and 3
# doesn't work because then the \r is a literal backslash-r
# instead of a carriage return.
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 valuelen = limit - len(fmt % '000') - len(b': \r\n')
Gregory Szorc
httppeer: extract code for HTTP header spanning...
r30759 result = []
n = 0
Gregory Szorc
global: use pycompat.xrange()...
r38806 for i in pycompat.xrange(0, len(value), valuelen):
Gregory Szorc
httppeer: extract code for HTTP header spanning...
r30759 n += 1
Augie Fackler
formatting: blacken the codebase...
r43346 result.append((fmt % str(n), pycompat.strurl(value[i : i + valuelen])))
Gregory Szorc
httppeer: extract code for HTTP header spanning...
r30759
return result
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
httppeer: add support for httppostargs when we're sending a file...
r33820 class _multifile(object):
def __init__(self, *fileobjs):
for f in fileobjs:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not util.safehasattr(f, b'length'):
Augie Fackler
httppeer: add support for httppostargs when we're sending a file...
r33820 raise ValueError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'_multifile only supports file objects that '
b'have a length but this one does not:',
Augie Fackler
formatting: blacken the codebase...
r43346 type(f),
f,
)
Augie Fackler
httppeer: add support for httppostargs when we're sending a file...
r33820 self._fileobjs = fileobjs
self._index = 0
@property
def length(self):
return sum(f.length for f in self._fileobjs)
def read(self, amt=None):
if amt <= 0:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b''.join(f.read() for f in self._fileobjs)
Augie Fackler
httppeer: add support for httppostargs when we're sending a file...
r33820 parts = []
while amt and self._index < len(self._fileobjs):
parts.append(self._fileobjs[self._index].read(amt))
got = len(parts[-1])
if got < amt:
self._index += 1
amt -= got
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b''.join(parts)
Augie Fackler
httppeer: add support for httppostargs when we're sending a file...
r33820
def seek(self, offset, whence=os.SEEK_SET):
if whence != os.SEEK_SET:
raise NotImplementedError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'_multifile does not support anything other'
b' than os.SEEK_SET for whence on seek()'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
httppeer: add support for httppostargs when we're sending a file...
r33820 if offset != 0:
raise NotImplementedError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'_multifile only supports seeking to start, but that '
b'could be fixed if you need it'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
httppeer: add support for httppostargs when we're sending a file...
r33820 for f in self._fileobjs:
f.seek(0)
self._index = 0
Augie Fackler
formatting: blacken the codebase...
r43346
def makev1commandrequest(
ui, requestbuilder, caps, capablefn, repobaseurl, cmd, args
):
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 """Make an HTTP request to run a command for a version 1 client.
``caps`` is a set of known server capabilities. The value may be
None if capabilities are not yet known.
``capablefn`` is a function to evaluate a capability.
``cmd``, ``args``, and ``data`` define the command, its arguments, and
raw data to pass to it.
"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if cmd == b'pushkey':
args[b'data'] = b''
data = args.pop(b'data', None)
headers = args.pop(b'headers', {})
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b"sending %s command\n" % cmd)
q = [(b'cmd', cmd)]
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 headersize = 0
# Important: don't use self.capable() here or else you end up
# with infinite recursion when trying to look up capabilities
# for the first time.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 postargsok = caps is not None and b'httppostargs' in caps
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567
# Send arguments via POST.
if postargsok and args:
strargs = urlreq.urlencode(sorted(args.items()))
if not data:
data = strargs
else:
if isinstance(data, bytes):
i = io.BytesIO(data)
i.length = len(data)
data = i
argsio = io.BytesIO(strargs)
argsio.length = len(strargs)
data = _multifile(argsio, data)
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 headers['X-HgArgs-Post'] = len(strargs)
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 elif args:
# Calling self.capable() can infinite loop if we are calling
# "capabilities". But that command should never accept wire
# protocol arguments. So this should never happen.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 assert cmd != b'capabilities'
httpheader = capablefn(b'httpheader')
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 if httpheader:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 headersize = int(httpheader.split(b',', 1)[0])
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567
# Send arguments via HTTP headers.
if headersize > 0:
# The headers can typically carry more data than the URL.
Martin von Zweigbergk
wireprotopeer: clarify some variable names now that we allow snake_case...
r47209 encoded_args = urlreq.urlencode(sorted(args.items()))
Augie Fackler
formatting: blacken the codebase...
r43346 for header, value in encodevalueinheaders(
Martin von Zweigbergk
wireprotopeer: clarify some variable names now that we allow snake_case...
r47209 encoded_args, b'X-HgArg', headersize
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 headers[header] = value
# Send arguments via query string (Mercurial <1.9).
else:
q += sorted(args.items())
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 qs = b'?%s' % urlreq.urlencode(q)
cu = b"%s%s" % (repobaseurl, qs)
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 size = 0
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if util.safehasattr(data, b'length'):
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 size = data.length
elif data is not None:
size = len(data)
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 if data is not None and 'Content-Type' not in headers:
headers['Content-Type'] = 'application/mercurial-0.1'
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567
# Tell the server we accept application/mercurial-0.2 and multiple
# compression formats if the server is capable of emitting those
# payloads.
Gregory Szorc
httppeer: only advertise partial-pull if capabilities are known...
r37574 # Note: Keep this set empty by default, as client advertisement of
# protocol parameters should only occur after the handshake.
protoparams = set()
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567
mediatypes = set()
if caps is not None:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 mt = capablefn(b'httpmediatype')
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 if mt:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 protoparams.add(b'0.1')
mediatypes = set(mt.split(b','))
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 protoparams.add(b'partial-pull')
Gregory Szorc
httppeer: only advertise partial-pull if capabilities are known...
r37574
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b'0.2tx' in mediatypes:
protoparams.add(b'0.2')
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b'0.2tx' in mediatypes and capablefn(b'compression'):
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 # We /could/ compare supported compression formats and prune
# non-mutually supported or error if nothing is mutually supported.
# For now, send the full list to the server and have it error.
Augie Fackler
formatting: blacken the codebase...
r43346 comps = [
e.wireprotosupport().name
for e in util.compengines.supportedwireengines(util.CLIENTROLE)
]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 protoparams.add(b'comp=%s' % b','.join(comps))
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567
if protoparams:
Augie Fackler
formatting: blacken the codebase...
r43346 protoheaders = encodevalueinheaders(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b' '.join(sorted(protoparams)), b'X-HgProto', headersize or 1024
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 for header, value in protoheaders:
headers[header] = value
Gregory Szorc
httppeer: always add x-hg* headers to Vary header...
r37573
varyheaders = []
for header in headers:
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 if header.lower().startswith('x-hg'):
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567 varyheaders.append(header)
if varyheaders:
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 headers['Vary'] = ','.join(sorted(varyheaders))
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567
req = requestbuilder(pycompat.strurl(cu), data, headers)
if data is not None:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b"sending %d bytes\n" % size)
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 req.add_unredirected_header('Content-Length', '%d' % size)
Gregory Szorc
httppeer: extract code for creating a request into own function...
r37567
return req, cu, qs
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
httppeer: work around API differences on urllib Request objects...
r37756 def _reqdata(req):
"""Get request data, if any. If no data, returns None."""
if pycompat.ispy3:
return req.data
if not req.has_data():
return None
return req.get_data()
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
httppeer: extract code for performing an HTTP request...
r37566 def sendrequest(ui, opener, req):
"""Send a prepared HTTP request.
Returns the response object.
"""
Boris Feld
httppeer: declare 'dbg' at the function level...
r38139 dbg = ui.debug
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if ui.debugflag and ui.configbool(b'devel', b'debug.peer-request'):
line = b'devel-peer-request: %s\n'
Augie Fackler
formatting: blacken the codebase...
r43346 dbg(
line
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 % b'%s %s'
Augie Fackler
formatting: blacken the codebase...
r43346 % (
pycompat.bytesurl(req.get_method()),
pycompat.bytesurl(req.get_full_url()),
)
)
Gregory Szorc
httppeer: extract code for performing an HTTP request...
r37566 hgargssize = None
for header, value in sorted(req.header_items()):
Augie Fackler
httppeer: no matter what Python 3 might think, http headers are bytes...
r37755 header = pycompat.bytesurl(header)
value = pycompat.bytesurl(value)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if header.startswith(b'X-hgarg-'):
Gregory Szorc
httppeer: extract code for performing an HTTP request...
r37566 if hgargssize is None:
hgargssize = 0
hgargssize += len(value)
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 dbg(line % b' %s %s' % (header, value))
Gregory Szorc
httppeer: extract code for performing an HTTP request...
r37566
if hgargssize is not None:
Augie Fackler
formatting: blacken the codebase...
r43346 dbg(
line
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 % b' %d bytes of commands arguments in headers'
Augie Fackler
formatting: blacken the codebase...
r43346 % hgargssize
)
Augie Fackler
httppeer: work around API differences on urllib Request objects...
r37756 data = _reqdata(req)
if data is not None:
Gregory Szorc
httppeer: extract code for performing an HTTP request...
r37566 length = getattr(data, 'length', None)
if length is None:
length = len(data)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 dbg(line % b' %d bytes of data' % length)
Gregory Szorc
httppeer: extract code for performing an HTTP request...
r37566
start = util.timer()
Martin von Zweigbergk
httppeer: fix use of uninitialized variable with devel logging...
r38521 res = None
Gregory Szorc
httppeer: move error handling and response wrapping into sendrequest...
r37568 try:
res = opener.open(req)
except urlerr.httperror as inst:
if inst.code == 401:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'authorization failed'))
Gregory Szorc
httppeer: move error handling and response wrapping into sendrequest...
r37568 raise
except httplib.HTTPException as inst:
Augie Fackler
formatting: blacken the codebase...
r43346 ui.debug(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'http error requesting %s\n'
% util.hidepassword(req.get_full_url())
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: move error handling and response wrapping into sendrequest...
r37568 ui.traceback()
raise IOError(None, inst)
finally:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if ui.debugflag and ui.configbool(b'devel', b'debug.peer-request'):
Martin von Zweigbergk
httppeer: fix use of uninitialized variable with devel logging...
r38521 code = res.code if res else -1
Augie Fackler
formatting: blacken the codebase...
r43346 dbg(
line
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 % b' finished in %.4f seconds (%d)'
Augie Fackler
formatting: blacken the codebase...
r43346 % (util.timer() - start, code)
)
Gregory Szorc
httppeer: move error handling and response wrapping into sendrequest...
r37568
# Insert error handlers for common I/O failures.
Gregory Szorc
url: move _wraphttpresponse() from httpeer...
r40054 urlmod.wrapresponse(res)
Gregory Szorc
httppeer: extract code for performing an HTTP request...
r37566
return res
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
httppeer: detect redirect to URL without query string (issue5860)...
r37851 class RedirectedRepoError(error.RepoError):
def __init__(self, msg, respurl):
super(RedirectedRepoError, self).__init__(msg)
self.respurl = respurl
Augie Fackler
formatting: blacken the codebase...
r43346
def parsev1commandresponse(
ui, baseurl, requrl, qs, resp, compressible, allowcbor=False
):
Gregory Szorc
httppeer: extract common response handling into own function...
r37569 # record the url we got redirected to
Gregory Szorc
httppeer: detect redirect to URL without query string (issue5860)...
r37851 redirected = False
Gregory Szorc
httppeer: extract common response handling into own function...
r37569 respurl = pycompat.bytesurl(resp.geturl())
if respurl.endswith(qs):
Augie Fackler
formatting: blacken the codebase...
r43346 respurl = respurl[: -len(qs)]
Gregory Szorc
httppeer: detect redirect to URL without query string (issue5860)...
r37851 qsdropped = False
else:
qsdropped = True
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if baseurl.rstrip(b'/') != respurl.rstrip(b'/'):
Gregory Szorc
httppeer: detect redirect to URL without query string (issue5860)...
r37851 redirected = True
Gregory Szorc
httppeer: extract common response handling into own function...
r37569 if not ui.quiet:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.warn(_(b'real URL is %s\n') % respurl)
Gregory Szorc
httppeer: extract common response handling into own function...
r37569
try:
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 proto = pycompat.bytesurl(resp.getheader('content-type', ''))
Gregory Szorc
httppeer: extract common response handling into own function...
r37569 except AttributeError:
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 proto = pycompat.bytesurl(resp.headers.get('content-type', ''))
Gregory Szorc
httppeer: extract common response handling into own function...
r37569
safeurl = util.hidepassword(baseurl)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if proto.startswith(b'application/hg-error'):
Gregory Szorc
httppeer: extract common response handling into own function...
r37569 raise error.OutOfBandError(resp.read())
Gregory Szorc
httppeer: don't accept very old media types (BC)...
r37572
# Pre 1.0 versions of Mercurial used text/plain and
# application/hg-changegroup. We don't support such old servers.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not proto.startswith(b'application/mercurial-'):
ui.debug(b"requested URL: '%s'\n" % util.hidepassword(requrl))
Augie Fackler
formatting: blacken the codebase...
r43346 msg = _(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"'%s' does not appear to be an hg repository:\n"
b"---%%<--- (%s)\n%s\n---%%<---\n"
) % (safeurl, proto or b'no content-type', resp.read(1024))
Gregory Szorc
httppeer: detect redirect to URL without query string (issue5860)...
r37851
# Some servers may strip the query string from the redirect. We
# raise a special error type so callers can react to this specially.
if redirected and qsdropped:
raise RedirectedRepoError(msg, respurl)
else:
raise error.RepoError(msg)
Gregory Szorc
httppeer: extract common response handling into own function...
r37569
Gregory Szorc
httppeer: don't accept very old media types (BC)...
r37572 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 subtype = proto.split(b'-', 1)[1]
Gregory Szorc
httppeer: support protocol upgrade...
r37576
# Unless we end up supporting CBOR in the legacy wire protocol,
# this should ONLY be encountered for the initial capabilities
# request during handshake.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if subtype == b'cbor':
Gregory Szorc
httppeer: support protocol upgrade...
r37576 if allowcbor:
return respurl, proto, resp
else:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.RepoError(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 _(b'unexpected CBOR response from server')
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: support protocol upgrade...
r37576
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 version_info = tuple([int(n) for n in subtype.split(b'.')])
Gregory Szorc
httppeer: don't accept very old media types (BC)...
r37572 except ValueError:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.RepoError(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 _(b"'%s' sent a broken Content-Type header (%s)") % (safeurl, proto)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: extract common response handling into own function...
r37569
Gregory Szorc
httppeer: don't accept very old media types (BC)...
r37572 # TODO consider switching to a decompression reader that uses
# generators.
if version_info == (0, 1):
if compressible:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 resp = util.compengines[b'zlib'].decompressorreader(resp)
Gregory Szorc
httppeer: extract common response handling into own function...
r37569
Gregory Szorc
httppeer: don't accept very old media types (BC)...
r37572 elif version_info == (0, 2):
# application/mercurial-0.2 always identifies the compression
# engine in the payload header.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elen = struct.unpack(b'B', util.readexactly(resp, 1))[0]
av6
httppeer: use util.readexactly() to abort on incomplete responses...
r39520 ename = util.readexactly(resp, elen)
Gregory Szorc
httppeer: don't accept very old media types (BC)...
r37572 engine = util.compengines.forwiretype(ename)
Gregory Szorc
httppeer: extract common response handling into own function...
r37569
Gregory Szorc
httppeer: don't accept very old media types (BC)...
r37572 resp = engine.decompressorreader(resp)
else:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.RepoError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b"'%s' uses newer protocol %s") % (safeurl, subtype)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: extract common response handling into own function...
r37569
Gregory Szorc
httppeer: support protocol upgrade...
r37576 return respurl, proto, resp
Gregory Szorc
httppeer: extract common response handling into own function...
r37569
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireproto: move version 1 peer functionality to standalone module (API)...
r37632 class httppeer(wireprotov1peer.wirepeer):
Gregory Szorc
httppeer: perform capabilities request in makepeer()...
r37570 def __init__(self, ui, path, url, opener, requestbuilder, caps):
Gregory Szorc
peer: make ui an attribute...
r37337 self.ui = ui
Gregory Szorc
httppeer: make several instance attributes internal (API)...
r33671 self._path = path
Gregory Szorc
httppeer: refactor how httppeer is created (API)...
r37024 self._url = url
Gregory Szorc
httppeer: perform capabilities request in makepeer()...
r37570 self._caps = caps
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self.limitedarguments = caps is not None and b'httppostargs' not in caps
Gregory Szorc
httppeer: refactor how httppeer is created (API)...
r37024 self._urlopener = opener
Gregory Szorc
httppeer: move requestbuilder defaults into makepeer() argument...
r37565 self._requestbuilder = requestbuilder
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
def __del__(self):
Gregory Szorc
httppeer: refactor how httppeer is created (API)...
r37024 for h in self._urlopener.handlers:
h.close()
getattr(h, "close_all", lambda: None)()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
repository: port peer interfaces to zope.interface...
r37336 # Begin of ipeerconnection interface.
Gregory Szorc
httppeer: use peer interface...
r33804
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 def url(self):
Gregory Szorc
httppeer: make several instance attributes internal (API)...
r33671 return self._path
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
httppeer: use peer interface...
r33804 def local(self):
return None
def peer(self):
return self
def canpush(self):
return True
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
httppeer: use peer interface...
r33804 def close(self):
Augie Fackler
http: work around custom http client classes that refuse extra attrs...
r40478 try:
Augie Fackler
formatting: blacken the codebase...
r43346 reqs, sent, recv = (
self._urlopener.requestscount,
self._urlopener.sentbytescount,
self._urlopener.receivedbytescount,
)
Augie Fackler
http: work around custom http client classes that refuse extra attrs...
r40478 except AttributeError:
return
Augie Fackler
formatting: blacken the codebase...
r43346 self.ui.note(
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'(sent %d HTTP requests and %d bytes; '
b'received %d bytes in responses)\n'
Augie Fackler
formatting: blacken the codebase...
r43346 )
% (reqs, sent, recv)
)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
repository: port peer interfaces to zope.interface...
r37336 # End of ipeerconnection interface.
Gregory Szorc
httppeer: use peer interface...
r33804
Gregory Szorc
repository: port peer interfaces to zope.interface...
r37336 # Begin of ipeercommands interface.
Gregory Szorc
httppeer: use peer interface...
r33804
def capabilities(self):
Gregory Szorc
httppeer: make several instance attributes internal (API)...
r33671 return self._caps
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
repository: port peer interfaces to zope.interface...
r37336 # End of ipeercommands interface.
Gregory Szorc
httppeer: use peer interface...
r33804
Gregory Szorc
httppeer: do decompression inside _callstream...
r30464 def _callstream(self, cmd, _compressible=False, **args):
Pulkit Goyal
py3: handle keyword arguments correctly in httppeer.py...
r35360 args = pycompat.byteskwargs(args)
Gregory Szorc
httppeer: change logic around argument handling...
r36236
Augie Fackler
formatting: blacken the codebase...
r43346 req, cu, qs = makev1commandrequest(
self.ui,
self._requestbuilder,
self._caps,
self.capable,
self._url,
cmd,
args,
)
Gregory Szorc
httppeer: advertise and support application/mercurial-0.2...
r30763
Gregory Szorc
httppeer: move error handling and response wrapping into sendrequest...
r37568 resp = sendrequest(self.ui, self._urlopener, req)
Gregory Szorc
httppeer: wrap HTTPResponse.read() globally...
r32002
Augie Fackler
formatting: blacken the codebase...
r43346 self._url, ct, resp = parsev1commandresponse(
self.ui, self._url, cu, qs, resp, _compressible
)
Gregory Szorc
httppeer: do decompression inside _callstream...
r30464
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 return resp
def _call(self, cmd, **args):
fp = self._callstream(cmd, **args)
try:
return fp.read()
finally:
# if using keepalive, allow connection to be reused
fp.close()
def _callpush(self, cmd, cg, **args):
# have to stream bundle to a temp file because we do not have
# http 1.1 chunked transfer.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 types = self.capable(b'unbundle')
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 types = types.split(b',')
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 except AttributeError:
# servers older than d1b16a746db6 will send 'unbundle' as a
# boolean capability. They only support headerless/uncompressed
# bundles.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 types = [b""]
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 for x in types:
Martin von Zweigbergk
bundle: move writebundle() from changegroup.py to bundle2.py (API)...
r28666 if x in bundle2.bundletypes:
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 type = x
break
Martin von Zweigbergk
bundle: move writebundle() from changegroup.py to bundle2.py (API)...
r28666 tempname = bundle2.writebundle(self.ui, cg, None, type)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fp = httpconnection.httpsendfile(self.ui, tempname, b"rb")
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 headers = {'Content-Type': 'application/mercurial-0.1'}
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
try:
Matt Mackall
httppeer: use try/except/finally
r25085 r = self._call(cmd, data=fp, headers=headers, **args)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 vals = r.split(b'\n', 1)
Matt Mackall
httppeer: use try/except/finally
r25085 if len(vals) < 2:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ResponseError(_(b"unexpected response:"), r)
Matt Mackall
httppeer: use try/except/finally
r25085 return vals
Augie Fackler
httppeer: explicitly catch urlerr.httperror and re-raise...
r36448 except urlerr.httperror:
# Catch and re-raise these so we don't try and treat them
# like generic socket errors. They lack any values in
# .args on Python 3 which breaks our socket.error block.
raise
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except socket.error as err:
Matt Mackall
httppeer: use try/except/finally
r25085 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'push failed: %s') % err.args[1])
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(err.args[1])
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 finally:
fp.close()
os.unlink(tempname)
Pierre-Yves David
httppeer: support for _calltwowaystream...
r21074 def _calltwowaystream(self, cmd, fp, **args):
filename = None
try:
# dump bundle to disk
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
Augie Fackler
cleanup: remove pointless r-prefixes on double-quoted strings...
r43809 with os.fdopen(fd, "wb") as fh:
Pierre-Yves David
httppeer: support for _calltwowaystream...
r21074 d = fp.read(4096)
Martin von Zweigbergk
httppeer: use context manager when writing temporary bundle to send...
r43118 while d:
fh.write(d)
d = fp.read(4096)
Pierre-Yves David
httppeer: support for _calltwowaystream...
r21074 # start http push
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 with httpconnection.httpsendfile(self.ui, filename, b"rb") as fp_:
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 headers = {'Content-Type': 'application/mercurial-0.1'}
Martin von Zweigbergk
httppeer: use context manager when reading temporary bundle to send...
r43119 return self._callstream(cmd, data=fp_, headers=headers, **args)
Pierre-Yves David
httppeer: support for _calltwowaystream...
r21074 finally:
Martin von Zweigbergk
httppeer: use context manager when writing temporary bundle to send...
r43118 if filename is not None:
Pierre-Yves David
httppeer: support for _calltwowaystream...
r21074 os.unlink(filename)
Pierre-Yves David
wireproto: drop the _decompress method in favor a new call type...
r20905 def _callcompressable(self, cmd, **args):
Gregory Szorc
httppeer: do decompression inside _callstream...
r30464 return self._callstream(cmd, _compressible=True, **args)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Mads Kiilerich
httppeer: reintroduce _abort that accidentally was removed in 167047ba3cfa...
r21188 def _abort(self, exception):
raise exception
Augie Fackler
formatting: blacken the codebase...
r43346
def sendv2request(
ui, opener, requestbuilder, apiurl, permission, requests, redirect
):
Gregory Szorc
wireprotov2: send protocol settings frame from client...
r40168 wireprotoframing.populatestreamencoders()
uiencoders = ui.configlist(b'experimental', b'httppeer.v2-encoder-order')
if uiencoders:
encoders = []
for encoder in uiencoders:
if encoder not in wireprotoframing.STREAM_ENCODERS:
Augie Fackler
formatting: blacken the codebase...
r43346 ui.warn(
_(
b'wire protocol version 2 encoder referenced in '
b'config (%s) is not known; ignoring\n'
)
% encoder
)
Gregory Szorc
wireprotov2: send protocol settings frame from client...
r40168 else:
encoders.append(encoder)
else:
encoders = wireprotoframing.STREAM_ENCODERS_ORDER
Augie Fackler
formatting: blacken the codebase...
r43346 reactor = wireprotoframing.clientreactor(
ui,
hasmultiplesend=False,
buffersends=True,
clientcontentencoders=encoders,
)
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
Augie Fackler
formatting: blacken the codebase...
r43346 handler = wireprotov2peer.clienthandler(
ui, reactor, opener=opener, requestbuilder=requestbuilder
)
Gregory Szorc
wireprotov2: move response handling out of httppeer...
r37737
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 url = b'%s/%s' % (apiurl, permission)
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
if len(requests) > 1:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 url += b'/multirequest'
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 url += b'/%s' % requests[0][0]
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b'sending %d commands\n' % len(requests))
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 for command, args, f in requests:
Augie Fackler
formatting: blacken the codebase...
r43346 ui.debug(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'sending command %s: %s\n'
Augie Fackler
formatting: blacken the codebase...
r43346 % (command, stringutil.pprint(args, indent=2))
)
assert not list(
handler.callcommand(command, args, f, redirect=redirect)
)
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
# TODO stream this.
Gregory Szorc
wireprotov2: move response handling out of httppeer...
r37737 body = b''.join(map(bytes, handler.flushcommands()))
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
# TODO modify user-agent to reflect v2
headers = {
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 'Accept': wireprotov2server.FRAMINGTYPE,
'Content-Type': wireprotov2server.FRAMINGTYPE,
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 }
req = requestbuilder(pycompat.strurl(url), body, headers)
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 req.add_unredirected_header('Content-Length', '%d' % len(body))
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
try:
res = opener.open(req)
except urlerr.httperror as e:
if e.code == 401:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'authorization failed'))
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
raise
except httplib.HTTPException as e:
ui.traceback()
raise IOError(None, e)
Gregory Szorc
wireprotov2: move response handling out of httppeer...
r37737 return handler, res
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 class queuedcommandfuture(pycompat.futures.Future):
"""Wraps result() on command futures to trigger submission on call."""
def result(self, timeout=None):
if self.done():
return pycompat.futures.Future.result(self, timeout)
self._peerexecutor.sendcommands()
# sendcommands() will restore the original __class__ and self.result
# will resolve to Future.result.
return self.result(timeout)
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
interfaceutil: module to stub out zope.interface...
r37828 @interfaceutil.implementer(repository.ipeercommandexecutor)
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 class httpv2executor(object):
Augie Fackler
formatting: blacken the codebase...
r43346 def __init__(
self, ui, opener, requestbuilder, apiurl, descriptor, redirect
):
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 self._ui = ui
self._opener = opener
self._requestbuilder = requestbuilder
self._apiurl = apiurl
self._descriptor = descriptor
Gregory Szorc
wireprotov2: client support for advertising redirect targets...
r40060 self._redirect = redirect
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 self._sent = False
self._closed = False
self._neededpermissions = set()
self._calls = []
self._futures = weakref.WeakSet()
self._responseexecutor = None
self._responsef = None
def __enter__(self):
return self
def __exit__(self, exctype, excvalue, exctb):
self.close()
def callcommand(self, command, args):
if self._sent:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 b'callcommand() cannot be used after commands are sent'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
if self._closed:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 b'callcommand() cannot be used after close()'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
# The service advertises which commands are available. So if we attempt
# to call an unknown command or pass an unknown argument, we can screen
# for this.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if command not in self._descriptor[b'commands']:
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'wire protocol command %s is not available' % command
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cmdinfo = self._descriptor[b'commands'][command]
unknownargs = set(args.keys()) - set(cmdinfo.get(b'args', {}))
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
if unknownargs:
raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'wire protocol command %s does not accept argument: %s'
% (command, b', '.join(sorted(unknownargs)))
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._neededpermissions |= set(cmdinfo[b'permissions'])
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
# TODO we /could/ also validate types here, since the API descriptor
# includes types...
f = pycompat.futures.Future()
# Monkeypatch it so result() triggers sendcommands(), otherwise result()
# could deadlock.
f.__class__ = queuedcommandfuture
f._peerexecutor = self
self._futures.add(f)
self._calls.append((command, args, f))
return f
def sendcommands(self):
if self._sent:
return
if not self._calls:
return
self._sent = True
# Unhack any future types so caller sees a clean type and so we
# break reference cycle.
for f in self._futures:
if isinstance(f, queuedcommandfuture):
f.__class__ = pycompat.futures.Future
f._peerexecutor = None
# Mark the future as running and filter out cancelled futures.
Augie Fackler
formatting: blacken the codebase...
r43346 calls = [
(command, args, f)
for command, args, f in self._calls
if f.set_running_or_notify_cancel()
]
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
# Clear out references, prevent improper object usage.
self._calls = None
if not calls:
return
permissions = set(self._neededpermissions)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b'push' in permissions and b'pull' in permissions:
permissions.remove(b'pull')
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
if len(permissions) > 1:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.RepoError(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 _(b'cannot make request requiring multiple permissions: %s')
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 % _(b', ').join(sorted(permissions))
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 permission = {
b'push': b'rw',
b'pull': b'ro',
}[permissions.pop()]
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
Gregory Szorc
wireprotov2: move response handling out of httppeer...
r37737 handler, resp = sendv2request(
Augie Fackler
formatting: blacken the codebase...
r43346 self._ui,
self._opener,
self._requestbuilder,
self._apiurl,
permission,
calls,
self._redirect,
)
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
# TODO we probably want to validate the HTTP code, media type, etc.
self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1)
Augie Fackler
formatting: blacken the codebase...
r43346 self._responsef = self._responseexecutor.submit(
self._handleresponse, handler, resp
)
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
def close(self):
if self._closed:
return
self.sendcommands()
self._closed = True
if not self._responsef:
return
Gregory Szorc
httppeer: add TODO about delayed handling of ^C...
r39468 # TODO ^C here may not result in immediate program termination.
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 try:
self._responsef.result()
finally:
self._responseexecutor.shutdown(wait=True)
self._responsef = None
self._responseexecutor = None
# If any of our futures are still in progress, mark them as
# errored, otherwise a result() could wait indefinitely.
for f in self._futures:
if not f.done():
Augie Fackler
formatting: blacken the codebase...
r43346 f.set_exception(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 error.ResponseError(_(b'unfulfilled command response'))
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
self._futures = None
Gregory Szorc
wireprotov2: move response handling out of httppeer...
r37737 def _handleresponse(self, handler, resp):
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 # Called in a thread to read the response.
Gregory Szorc
wireprotov2: change name and behavior of readframe()...
r40055 while handler.readdata(resp):
Gregory Szorc
wireprotov2: move response handling out of httppeer...
r37737 pass
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
httppeer: expose API descriptor on httpv2peer...
r40207 @interfaceutil.implementer(repository.ipeerv2)
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501 class httpv2peer(object):
peer: introduce a limitedarguments attributes...
r42334
limitedarguments = False
Augie Fackler
formatting: blacken the codebase...
r43346 def __init__(
self, ui, repourl, apipath, opener, requestbuilder, apidescriptor
):
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501 self.ui = ui
Gregory Szorc
httppeer: expose API descriptor on httpv2peer...
r40207 self.apidescriptor = apidescriptor
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if repourl.endswith(b'/'):
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501 repourl = repourl[:-1]
Gregory Szorc
httppeer: implement ipeerconnection...
r37627 self._url = repourl
Gregory Szorc
httppeer: support protocol upgrade...
r37576 self._apipath = apipath
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._apiurl = b'%s/%s' % (repourl, apipath)
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501 self._opener = opener
Gregory Szorc
httppeer: support protocol upgrade...
r37576 self._requestbuilder = requestbuilder
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501
Gregory Szorc
wireprotov2: client support for advertising redirect targets...
r40060 self._redirect = wireprotov2peer.supportedredirects(ui, apidescriptor)
Gregory Szorc
httppeer: implement ipeerconnection...
r37627 # Start of ipeerconnection.
def url(self):
return self._url
def local(self):
return None
def peer(self):
return self
def canpush(self):
# TODO change once implemented.
return False
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501 def close(self):
Augie Fackler
formatting: blacken the codebase...
r43346 self.ui.note(
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'(sent %d HTTP requests and %d bytes; '
b'received %d bytes in responses)\n'
Augie Fackler
formatting: blacken the codebase...
r43346 )
% (
self._opener.requestscount,
self._opener.sentbytescount,
self._opener.receivedbytescount,
)
)
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501
Gregory Szorc
httppeer: implement ipeerconnection...
r37627 # End of ipeerconnection.
Gregory Szorc
httppeer: basic implementation of capabilities interface...
r37629 # Start of ipeercapabilities.
def capable(self, name):
# The capabilities used internally historically map to capabilities
# advertised from the "capabilities" wire protocol command. However,
# version 2 of that command works differently.
# Maps to commands that are available.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if name in (
b'branchmap',
b'getbundle',
b'known',
b'lookup',
b'pushkey',
):
Gregory Szorc
httppeer: basic implementation of capabilities interface...
r37629 return True
# Other concepts.
Raphaël Gomès
wireprotov2: re-add tuple around `bundle2` check...
r46668 if name in (b'bundle2',):
Gregory Szorc
httppeer: basic implementation of capabilities interface...
r37629 return True
Gregory Szorc
httppeer: expose capabilities for each command...
r39664 # Alias command-* to presence of command of that name.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if name.startswith(b'command-'):
return name[len(b'command-') :] in self.apidescriptor[b'commands']
Gregory Szorc
httppeer: expose capabilities for each command...
r39664
Gregory Szorc
httppeer: basic implementation of capabilities interface...
r37629 return False
def requirecap(self, name, purpose):
if self.capable(name):
return
raise error.CapabilityError(
Augie Fackler
formatting: blacken the codebase...
r43346 _(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'cannot %s; client or remote repository does not support the '
b'\'%s\' capability'
Augie Fackler
formatting: blacken the codebase...
r43346 )
% (purpose, name)
)
Gregory Szorc
httppeer: basic implementation of capabilities interface...
r37629
# End of ipeercapabilities.
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501 def _call(self, name, **args):
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 with self.commandexecutor() as e:
return e.callcommand(name, args).result()
Gregory Szorc
wireproto: introduce a reactor for client-side state...
r37561
Gregory Szorc
httppeer: implement command executor for version 2 peer...
r37669 def commandexecutor(self):
Augie Fackler
formatting: blacken the codebase...
r43346 return httpv2executor(
self.ui,
self._opener,
self._requestbuilder,
self._apiurl,
self.apidescriptor,
self._redirect,
)
Gregory Szorc
wireproto: crude support for version 2 HTTP peer...
r37501
Gregory Szorc
httppeer: support protocol upgrade...
r37576 # Registry of API service names to metadata about peers that handle it.
#
# The following keys are meaningful:
#
# init
# Callable receiving (ui, repourl, servicepath, opener, requestbuilder,
# apidescriptor) to create a peer.
#
# priority
# Integer priority for the service. If we could choose from multiple
# services, we choose the one with the highest priority.
API_PEERS = {
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 wireprototypes.HTTP_WIREPROTO_V2: {
b'init': httpv2peer,
b'priority': 50,
},
Gregory Szorc
httppeer: support protocol upgrade...
r37576 }
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
httppeer: perform capabilities request in makepeer()...
r37570 def performhandshake(ui, url, opener, requestbuilder):
# The handshake is a request to the capabilities command.
caps = None
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
httppeer: perform capabilities request in makepeer()...
r37570 def capable(x):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ProgrammingError(b'should not be called')
Gregory Szorc
httppeer: perform capabilities request in makepeer()...
r37570
Gregory Szorc
httppeer: support protocol upgrade...
r37576 args = {}
# The client advertises support for newer protocols by adding an
# X-HgUpgrade-* header with a list of supported APIs and an
# X-HgProto-* header advertising which serializing formats it supports.
# We only support the HTTP version 2 transport and CBOR responses for
# now.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 advertisev2 = ui.configbool(b'experimental', b'httppeer.advertise-v2')
Gregory Szorc
httppeer: support protocol upgrade...
r37576
if advertisev2:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 args[b'headers'] = {
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 'X-HgProto-1': 'cbor',
Gregory Szorc
httppeer: support protocol upgrade...
r37576 }
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 args[b'headers'].update(
Augie Fackler
formatting: blacken the codebase...
r43346 encodevalueinheaders(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b' '.join(sorted(API_PEERS)),
b'X-HgUpgrade',
Augie Fackler
formatting: blacken the codebase...
r43346 # We don't know the header limit this early.
# So make it small.
1024,
)
)
Gregory Szorc
httppeer: support protocol upgrade...
r37576
Augie Fackler
formatting: blacken the codebase...
r43346 req, requrl, qs = makev1commandrequest(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui, requestbuilder, caps, capable, url, b'capabilities', args
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: perform capabilities request in makepeer()...
r37570 resp = sendrequest(ui, opener, req)
Gregory Szorc
httppeer: detect redirect to URL without query string (issue5860)...
r37851 # The server may redirect us to the repo root, stripping the
# ?cmd=capabilities query string from the URL. The server would likely
# return HTML in this case and ``parsev1commandresponse()`` would raise.
# We catch this special case and re-issue the capabilities request against
# the new URL.
#
# We should ideally not do this, as a redirect that drops the query
# string from the URL is arguably a server bug. (Garbage in, garbage out).
# However, Mercurial clients for several years appeared to handle this
# issue without behavior degradation. And according to issue 5860, it may
# be a longstanding bug in some server implementations. So we allow a
# redirect that drops the query string to "just work."
try:
Augie Fackler
formatting: blacken the codebase...
r43346 respurl, ct, resp = parsev1commandresponse(
ui, url, requrl, qs, resp, compressible=False, allowcbor=advertisev2
)
Gregory Szorc
httppeer: detect redirect to URL without query string (issue5860)...
r37851 except RedirectedRepoError as e:
Augie Fackler
formatting: blacken the codebase...
r43346 req, requrl, qs = makev1commandrequest(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui, requestbuilder, caps, capable, e.respurl, b'capabilities', args
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: detect redirect to URL without query string (issue5860)...
r37851 resp = sendrequest(ui, opener, req)
Augie Fackler
formatting: blacken the codebase...
r43346 respurl, ct, resp = parsev1commandresponse(
ui, url, requrl, qs, resp, compressible=False, allowcbor=advertisev2
)
Gregory Szorc
httppeer: perform capabilities request in makepeer()...
r37570
try:
Gregory Szorc
httppeer: support protocol upgrade...
r37576 rawdata = resp.read()
Gregory Szorc
httppeer: perform capabilities request in makepeer()...
r37570 finally:
resp.close()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not ct.startswith(b'application/mercurial-'):
raise error.ProgrammingError(b'unexpected content-type: %s' % ct)
Gregory Szorc
httppeer: support protocol upgrade...
r37576
if advertisev2:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if ct == b'application/mercurial-cbor':
Gregory Szorc
httppeer: support protocol upgrade...
r37576 try:
Gregory Szorc
httppeer: use our CBOR decoder...
r39476 info = cborutil.decodeall(rawdata)[0]
except cborutil.CBORDecodeError:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'error decoding CBOR from remote server'),
Augie Fackler
formatting: blacken the codebase...
r43346 hint=_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'try again and consider contacting '
b'the server operator'
Augie Fackler
formatting: blacken the codebase...
r43346 ),
)
Gregory Szorc
httppeer: support protocol upgrade...
r37576
# We got a legacy response. That's fine.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif ct in (b'application/mercurial-0.1', b'application/mercurial-0.2'):
info = {b'v1capabilities': set(rawdata.split())}
Gregory Szorc
httppeer: support protocol upgrade...
r37576
else:
raise error.RepoError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'unexpected response type from server: %s') % ct
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: support protocol upgrade...
r37576 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 info = {b'v1capabilities': set(rawdata.split())}
Gregory Szorc
httppeer: support protocol upgrade...
r37576
return respurl, info
Gregory Szorc
httppeer: perform capabilities request in makepeer()...
r37570
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
httppeer: allow opener to be passed to makepeer()...
r37571 def makepeer(ui, path, opener=None, requestbuilder=urlreq.request):
Gregory Szorc
httppeer: move requestbuilder defaults into makepeer() argument...
r37565 """Construct an appropriate HTTP peer instance.
Gregory Szorc
httppeer: allow opener to be passed to makepeer()...
r37571 ``opener`` is an ``url.opener`` that should be used to establish
connections, perform HTTP requests.
Gregory Szorc
httppeer: move requestbuilder defaults into makepeer() argument...
r37565 ``requestbuilder`` is the type used for constructing HTTP requests.
It exists as an argument so extensions can override the default.
"""
Gregory Szorc
httppeer: refactor how httppeer is created (API)...
r37024 u = util.url(path)
if u.query or u.fragment:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'unsupported URL component: "%s"') % (u.query or u.fragment)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: refactor how httppeer is created (API)...
r37024
# urllib cannot handle URLs with embedded user or passwd.
url, authinfo = u.authinfo()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b'using %s\n' % url)
Gregory Szorc
httppeer: refactor how httppeer is created (API)...
r37024
Gregory Szorc
httppeer: allow opener to be passed to makepeer()...
r37571 opener = opener or urlmod.opener(ui, authinfo)
Gregory Szorc
httppeer: refactor how httppeer is created (API)...
r37024
Gregory Szorc
httppeer: support protocol upgrade...
r37576 respurl, info = performhandshake(ui, url, opener, requestbuilder)
# Given the intersection of APIs that both we and the server support,
# sort by their advertised priority and pick the first one.
#
# TODO consider making this request-based and interface driven. For
# example, the caller could say "I want a peer that does X." It's quite
# possible that not all peers would do that. Since we know the service
# capabilities, we could filter out services not meeting the
# requirements. Possibly by consulting the interfaces defined by the
# peer type.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 apipeerchoices = set(info.get(b'apis', {}).keys()) & set(API_PEERS.keys())
Gregory Szorc
httppeer: perform capabilities request in makepeer()...
r37570
Augie Fackler
formatting: blacken the codebase...
r43346 preferredchoices = sorted(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 apipeerchoices, key=lambda x: API_PEERS[x][b'priority'], reverse=True
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: support protocol upgrade...
r37576
for service in preferredchoices:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 apipath = b'%s/%s' % (info[b'apibase'].rstrip(b'/'), service)
Gregory Szorc
httppeer: support protocol upgrade...
r37576
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return API_PEERS[service][b'init'](
ui, respurl, apipath, opener, requestbuilder, info[b'apis'][service]
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: support protocol upgrade...
r37576
# Failed to construct an API peer. Fall back to legacy.
Augie Fackler
formatting: blacken the codebase...
r43346 return httppeer(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui, path, respurl, opener, requestbuilder, info[b'v1capabilities']
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: refactor how httppeer is created (API)...
r37024
Gregory Szorc
hg: allow extra arguments to be passed to repo creation (API)...
r39585 def instance(ui, path, create, intents=None, createopts=None):
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if create:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'cannot create new http repository'))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if path.startswith(b'https:') and not urlmod.has_https:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 _(b'Python support for SSL and HTTPS is not installed')
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
httppeer: remove support for connecting to <0.9.1 servers (BC)...
r35902
Gregory Szorc
httppeer: refactor how httppeer is created (API)...
r37024 inst = makepeer(ui, path)
Gregory Szorc
httppeer: remove support for connecting to <0.9.1 servers (BC)...
r35902
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 return inst
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except error.RepoError as httpexception:
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 r = statichttprepo.instance(ui, b"static-" + path, create)
ui.note(_(b'(falling back to static-http)\n'))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 return r
except error.RepoError:
Augie Fackler
formatting: blacken the codebase...
r43346 raise httpexception # use the original http RepoError instead