##// END OF EJS Templates
destupdate: extract logic based on obsolescence marker in its own function...
destupdate: extract logic based on obsolescence marker in its own function One of the main goal of having consolidated destination function is to allow extension to play with this logic. We extract sub logic to make is wrapping more practical.

File last commit:

r26587:56b2bcea default
r26723:52d08a93 default
Show More
httppeer.py
292 lines | 10.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
import httplib
import os
import socket
Pierre-Yves David
httppeer: support for _calltwowaystream...
r21074 import tempfile
Gregory Szorc
httppeer: use absolute_import
r25954 import urllib
import urllib2
import zlib
from .i18n import _
from .node import nullid
from . import (
changegroup,
error,
httpconnection,
statichttprepo,
url,
util,
wireproto,
)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
def zgenerator(f):
zd = zlib.decompressobj()
try:
for chunk in util.filechunkiter(f):
while chunk:
yield zd.decompress(chunk, 2**18)
chunk = zd.unconsumed_tail
except httplib.HTTPException:
raise IOError(None, _('connection ended unexpectedly'))
yield zd.flush()
class httppeer(wireproto.wirepeer):
def __init__(self, ui, path):
self.path = path
self.caps = None
self.handler = None
self.urlopener = None
Kyle Lippincott
httppeer: allow extensions to replace urllib2.Request...
r25500 self.requestbuilder = None
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 u = util.url(path)
if u.query or u.fragment:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('unsupported URL component: "%s"') %
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 (u.query or u.fragment))
# urllib cannot handle URLs with embedded user or passwd
self._url, authinfo = u.authinfo()
self.ui = ui
self.ui.debug('using %s\n' % self._url)
self.urlopener = url.opener(ui, authinfo)
Kyle Lippincott
httppeer: allow extensions to replace urllib2.Request...
r25500 self.requestbuilder = urllib2.Request
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
def __del__(self):
if self.urlopener:
for h in self.urlopener.handlers:
h.close()
getattr(h, "close_all", lambda : None)()
def url(self):
return self.path
# look up capabilities only when needed
def _fetchcaps(self):
self.caps = set(self._call('capabilities').split())
def _capabilities(self):
if self.caps is None:
try:
self._fetchcaps()
except error.RepoError:
self.caps = set()
self.ui.debug('capabilities: %s\n' %
(' '.join(self.caps or ['none'])))
return self.caps
def lock(self):
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('operation not supported over http'))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
def _callstream(self, cmd, **args):
if cmd == 'pushkey':
args['data'] = ''
data = args.pop('data', None)
size = 0
if util.safehasattr(data, 'length'):
size = data.length
elif data is not None:
size = len(data)
headers = args.pop('headers', {})
Pierre-Yves.David@ens-lyon.org
httprepo: ensure Content-Type header exists when pushing data...
r17221 if data is not None and 'Content-Type' not in headers:
headers['Content-Type'] = 'application/mercurial-0.1'
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
if size and self.ui.configbool('ui', 'usehttp2', False):
headers['Expect'] = '100-Continue'
headers['X-HgHttp2'] = '1'
self.ui.debug("sending %s command\n" % cmd)
q = [('cmd', cmd)]
headersize = 0
if len(args) > 0:
httpheader = self.capable('httpheader')
if httpheader:
headersize = int(httpheader.split(',')[0])
if headersize > 0:
# The headers can typically carry more data than the URL.
encargs = urllib.urlencode(sorted(args.items()))
headerfmt = 'X-HgArg-%s'
contentlen = headersize - len(headerfmt % '000' + ': \r\n')
headernum = 0
for i in xrange(0, len(encargs), contentlen):
headernum += 1
header = headerfmt % str(headernum)
headers[header] = encargs[i:i + contentlen]
varyheaders = [headerfmt % str(h) for h in range(1, headernum + 1)]
headers['Vary'] = ','.join(varyheaders)
else:
q += sorted(args.items())
qs = '?%s' % urllib.urlencode(q)
cu = "%s%s" % (self._url, qs)
Kyle Lippincott
httppeer: allow extensions to replace urllib2.Request...
r25500 req = self.requestbuilder(cu, data, headers)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if data is not None:
self.ui.debug("sending %s bytes\n" % size)
req.add_unredirected_header('Content-Length', '%d' % size)
try:
resp = self.urlopener.open(req)
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except urllib2.HTTPError as inst:
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if inst.code == 401:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('authorization failed'))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 raise
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except httplib.HTTPException as inst:
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 self.ui.debug('http error while sending %s command\n' % cmd)
self.ui.traceback()
raise IOError(None, inst)
except IndexError:
# this only happens with Python 2.3, later versions raise URLError
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('http error, possibly caused by proxy setting'))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 # record the url we got redirected to
resp_url = resp.geturl()
if resp_url.endswith(qs):
resp_url = resp_url[:-len(qs)]
if self._url.rstrip('/') != resp_url.rstrip('/'):
if not self.ui.quiet:
self.ui.warn(_('real URL is %s\n') % resp_url)
self._url = resp_url
try:
proto = resp.getheader('content-type')
except AttributeError:
proto = resp.headers.get('content-type', '')
safeurl = util.hidepassword(self._url)
if proto.startswith('application/hg-error'):
raise error.OutOfBandError(resp.read())
# accept old "text/plain" and "application/hg-changegroup" for now
if not (proto.startswith('application/mercurial-') or
Matt Mackall
httppeer: improve protocol check...
r18737 (proto.startswith('text/plain')
and not resp.headers.get('content-length')) or
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 proto.startswith('application/hg-changegroup')):
self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
raise error.RepoError(
_("'%s' does not appear to be an hg repository:\n"
"---%%<--- (%s)\n%s\n---%%<---\n")
Matt Mackall
httppeer: avoid large dumps when we don't see an hgweb repo...
r18738 % (safeurl, proto or 'no content-type', resp.read(1024)))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
if proto.startswith('application/mercurial-'):
try:
version = proto.split('-', 1)[1]
version_info = tuple([int(n) for n in version.split('.')])
except ValueError:
raise error.RepoError(_("'%s' sent a broken Content-Type "
"header (%s)") % (safeurl, proto))
if version_info > (0, 1):
raise error.RepoError(_("'%s' uses newer protocol %s") %
(safeurl, version))
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.
types = self.capable('unbundle')
try:
types = types.split(',')
except AttributeError:
# servers older than d1b16a746db6 will send 'unbundle' as a
# boolean capability. They only support headerless/uncompressed
# bundles.
types = [""]
for x in types:
if x in changegroup.bundletypes:
type = x
break
Eric Sumner
changegroup.writebundle: provide ui...
r23895 tempname = changegroup.writebundle(self.ui, cg, None, type)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
headers = {'Content-Type': 'application/mercurial-0.1'}
try:
Matt Mackall
httppeer: use try/except/finally
r25085 r = self._call(cmd, data=fp, headers=headers, **args)
vals = r.split('\n', 1)
if len(vals) < 2:
raise error.ResponseError(_("unexpected response:"), r)
return vals
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):
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('push failed: %s') % err.args[1])
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):
fh = None
Matt Harbison
httppeer: close the temporary bundle file after two-way streaming it...
r23086 fp_ = None
Pierre-Yves David
httppeer: support for _calltwowaystream...
r21074 filename = None
try:
# dump bundle to disk
fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
fh = os.fdopen(fd, "wb")
d = fp.read(4096)
while d:
fh.write(d)
d = fp.read(4096)
fh.close()
# start http push
Matt Harbison
httppeer: close the temporary bundle file after two-way streaming it...
r23086 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
Pierre-Yves David
httppeer: support for _calltwowaystream...
r21074 headers = {'Content-Type': 'application/mercurial-0.1'}
Matt Harbison
httppeer: close the temporary bundle file after two-way streaming it...
r23086 return self._callstream(cmd, data=fp_, headers=headers, **args)
Pierre-Yves David
httppeer: support for _calltwowaystream...
r21074 finally:
Matt Harbison
httppeer: close the temporary bundle file after two-way streaming it...
r23086 if fp_ is not None:
fp_.close()
Pierre-Yves David
httppeer: support for _calltwowaystream...
r21074 if fh is not None:
fh.close()
os.unlink(filename)
Pierre-Yves David
wireproto: drop the _decompress method in favor a new call type...
r20905 def _callcompressable(self, cmd, **args):
stream = self._callstream(cmd, **args)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 return util.chunkbuffer(zgenerator(stream))
Mads Kiilerich
httppeer: reintroduce _abort that accidentally was removed in 167047ba3cfa...
r21188 def _abort(self, exception):
raise exception
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 class httpspeer(httppeer):
def __init__(self, ui, path):
if not url.has_https:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('Python support for SSL and HTTPS '
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 'is not installed'))
httppeer.__init__(self, ui, path)
def instance(ui, path, create):
if create:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('cannot create new http repository'))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 try:
if path.startswith('https:'):
inst = httpspeer(ui, path)
else:
inst = httppeer(ui, path)
try:
# Try to do useful work when checking compatibility.
# Usually saves a roundtrip since we want the caps anyway.
inst._fetchcaps()
except error.RepoError:
# No luck, try older compatibility check.
inst.between([(nullid, nullid)])
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:
r = statichttprepo.instance(ui, "static-" + path, create)
ui.note('(falling back to static-http)\n')
return r
except error.RepoError:
raise httpexception # use the original http RepoError instead