##// END OF EJS Templates
httppeer: always produce native str header keys and values...
Augie Fackler -
r34733:67e9678e default
parent child Browse files
Show More
@@ -1,464 +1,472
1 # httppeer.py - HTTP repository proxy classes for mercurial
1 # httppeer.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import io
12 import io
13 import os
13 import os
14 import socket
14 import socket
15 import struct
15 import struct
16 import tempfile
16 import tempfile
17
17
18 from .i18n import _
18 from .i18n import _
19 from .node import nullid
19 from .node import nullid
20 from . import (
20 from . import (
21 bundle2,
21 bundle2,
22 error,
22 error,
23 httpconnection,
23 httpconnection,
24 pycompat,
24 pycompat,
25 statichttprepo,
25 statichttprepo,
26 url,
26 url,
27 util,
27 util,
28 wireproto,
28 wireproto,
29 )
29 )
30
30
31 httplib = util.httplib
31 httplib = util.httplib
32 urlerr = util.urlerr
32 urlerr = util.urlerr
33 urlreq = util.urlreq
33 urlreq = util.urlreq
34
34
35 def encodevalueinheaders(value, header, limit):
35 def encodevalueinheaders(value, header, limit):
36 """Encode a string value into multiple HTTP headers.
36 """Encode a string value into multiple HTTP headers.
37
37
38 ``value`` will be encoded into 1 or more HTTP headers with the names
38 ``value`` will be encoded into 1 or more HTTP headers with the names
39 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
39 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
40 name + value will be at most ``limit`` bytes long.
40 name + value will be at most ``limit`` bytes long.
41
41
42 Returns an iterable of 2-tuples consisting of header names and values.
42 Returns an iterable of 2-tuples consisting of header names and
43 values as native strings.
43 """
44 """
44 fmt = header + '-%s'
45 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
45 valuelen = limit - len(fmt % '000') - len(': \r\n')
46 # not bytes. This function always takes bytes in as arguments.
47 fmt = pycompat.strurl(header) + r'-%s'
48 # Note: it is *NOT* a bug that the last bit here is a bytestring
49 # and not a unicode: we're just getting the encoded length anyway,
50 # and using an r-string to make it portable between Python 2 and 3
51 # doesn't work because then the \r is a literal backslash-r
52 # instead of a carriage return.
53 valuelen = limit - len(fmt % r'000') - len(': \r\n')
46 result = []
54 result = []
47
55
48 n = 0
56 n = 0
49 for i in xrange(0, len(value), valuelen):
57 for i in xrange(0, len(value), valuelen):
50 n += 1
58 n += 1
51 result.append((fmt % str(n), value[i:i + valuelen]))
59 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
52
60
53 return result
61 return result
54
62
55 def _wraphttpresponse(resp):
63 def _wraphttpresponse(resp):
56 """Wrap an HTTPResponse with common error handlers.
64 """Wrap an HTTPResponse with common error handlers.
57
65
58 This ensures that any I/O from any consumer raises the appropriate
66 This ensures that any I/O from any consumer raises the appropriate
59 error and messaging.
67 error and messaging.
60 """
68 """
61 origread = resp.read
69 origread = resp.read
62
70
63 class readerproxy(resp.__class__):
71 class readerproxy(resp.__class__):
64 def read(self, size=None):
72 def read(self, size=None):
65 try:
73 try:
66 return origread(size)
74 return origread(size)
67 except httplib.IncompleteRead as e:
75 except httplib.IncompleteRead as e:
68 # e.expected is an integer if length known or None otherwise.
76 # e.expected is an integer if length known or None otherwise.
69 if e.expected:
77 if e.expected:
70 msg = _('HTTP request error (incomplete response; '
78 msg = _('HTTP request error (incomplete response; '
71 'expected %d bytes got %d)') % (e.expected,
79 'expected %d bytes got %d)') % (e.expected,
72 len(e.partial))
80 len(e.partial))
73 else:
81 else:
74 msg = _('HTTP request error (incomplete response)')
82 msg = _('HTTP request error (incomplete response)')
75
83
76 raise error.PeerTransportError(
84 raise error.PeerTransportError(
77 msg,
85 msg,
78 hint=_('this may be an intermittent network failure; '
86 hint=_('this may be an intermittent network failure; '
79 'if the error persists, consider contacting the '
87 'if the error persists, consider contacting the '
80 'network or server operator'))
88 'network or server operator'))
81 except httplib.HTTPException as e:
89 except httplib.HTTPException as e:
82 raise error.PeerTransportError(
90 raise error.PeerTransportError(
83 _('HTTP request error (%s)') % e,
91 _('HTTP request error (%s)') % e,
84 hint=_('this may be an intermittent network failure; '
92 hint=_('this may be an intermittent network failure; '
85 'if the error persists, consider contacting the '
93 'if the error persists, consider contacting the '
86 'network or server operator'))
94 'network or server operator'))
87
95
88 resp.__class__ = readerproxy
96 resp.__class__ = readerproxy
89
97
90 class _multifile(object):
98 class _multifile(object):
91 def __init__(self, *fileobjs):
99 def __init__(self, *fileobjs):
92 for f in fileobjs:
100 for f in fileobjs:
93 if not util.safehasattr(f, 'length'):
101 if not util.safehasattr(f, 'length'):
94 raise ValueError(
102 raise ValueError(
95 '_multifile only supports file objects that '
103 '_multifile only supports file objects that '
96 'have a length but this one does not:', type(f), f)
104 'have a length but this one does not:', type(f), f)
97 self._fileobjs = fileobjs
105 self._fileobjs = fileobjs
98 self._index = 0
106 self._index = 0
99
107
100 @property
108 @property
101 def length(self):
109 def length(self):
102 return sum(f.length for f in self._fileobjs)
110 return sum(f.length for f in self._fileobjs)
103
111
104 def read(self, amt=None):
112 def read(self, amt=None):
105 if amt <= 0:
113 if amt <= 0:
106 return ''.join(f.read() for f in self._fileobjs)
114 return ''.join(f.read() for f in self._fileobjs)
107 parts = []
115 parts = []
108 while amt and self._index < len(self._fileobjs):
116 while amt and self._index < len(self._fileobjs):
109 parts.append(self._fileobjs[self._index].read(amt))
117 parts.append(self._fileobjs[self._index].read(amt))
110 got = len(parts[-1])
118 got = len(parts[-1])
111 if got < amt:
119 if got < amt:
112 self._index += 1
120 self._index += 1
113 amt -= got
121 amt -= got
114 return ''.join(parts)
122 return ''.join(parts)
115
123
116 def seek(self, offset, whence=os.SEEK_SET):
124 def seek(self, offset, whence=os.SEEK_SET):
117 if whence != os.SEEK_SET:
125 if whence != os.SEEK_SET:
118 raise NotImplementedError(
126 raise NotImplementedError(
119 '_multifile does not support anything other'
127 '_multifile does not support anything other'
120 ' than os.SEEK_SET for whence on seek()')
128 ' than os.SEEK_SET for whence on seek()')
121 if offset != 0:
129 if offset != 0:
122 raise NotImplementedError(
130 raise NotImplementedError(
123 '_multifile only supports seeking to start, but that '
131 '_multifile only supports seeking to start, but that '
124 'could be fixed if you need it')
132 'could be fixed if you need it')
125 for f in self._fileobjs:
133 for f in self._fileobjs:
126 f.seek(0)
134 f.seek(0)
127 self._index = 0
135 self._index = 0
128
136
129 class httppeer(wireproto.wirepeer):
137 class httppeer(wireproto.wirepeer):
130 def __init__(self, ui, path):
138 def __init__(self, ui, path):
131 self._path = path
139 self._path = path
132 self._caps = None
140 self._caps = None
133 self._urlopener = None
141 self._urlopener = None
134 self._requestbuilder = None
142 self._requestbuilder = None
135 u = util.url(path)
143 u = util.url(path)
136 if u.query or u.fragment:
144 if u.query or u.fragment:
137 raise error.Abort(_('unsupported URL component: "%s"') %
145 raise error.Abort(_('unsupported URL component: "%s"') %
138 (u.query or u.fragment))
146 (u.query or u.fragment))
139
147
140 # urllib cannot handle URLs with embedded user or passwd
148 # urllib cannot handle URLs with embedded user or passwd
141 self._url, authinfo = u.authinfo()
149 self._url, authinfo = u.authinfo()
142
150
143 self._ui = ui
151 self._ui = ui
144 ui.debug('using %s\n' % self._url)
152 ui.debug('using %s\n' % self._url)
145
153
146 self._urlopener = url.opener(ui, authinfo)
154 self._urlopener = url.opener(ui, authinfo)
147 self._requestbuilder = urlreq.request
155 self._requestbuilder = urlreq.request
148
156
149 def __del__(self):
157 def __del__(self):
150 urlopener = getattr(self, '_urlopener', None)
158 urlopener = getattr(self, '_urlopener', None)
151 if urlopener:
159 if urlopener:
152 for h in urlopener.handlers:
160 for h in urlopener.handlers:
153 h.close()
161 h.close()
154 getattr(h, "close_all", lambda: None)()
162 getattr(h, "close_all", lambda: None)()
155
163
156 # Begin of _basepeer interface.
164 # Begin of _basepeer interface.
157
165
158 @util.propertycache
166 @util.propertycache
159 def ui(self):
167 def ui(self):
160 return self._ui
168 return self._ui
161
169
162 def url(self):
170 def url(self):
163 return self._path
171 return self._path
164
172
165 def local(self):
173 def local(self):
166 return None
174 return None
167
175
168 def peer(self):
176 def peer(self):
169 return self
177 return self
170
178
171 def canpush(self):
179 def canpush(self):
172 return True
180 return True
173
181
174 def close(self):
182 def close(self):
175 pass
183 pass
176
184
177 # End of _basepeer interface.
185 # End of _basepeer interface.
178
186
179 # Begin of _basewirepeer interface.
187 # Begin of _basewirepeer interface.
180
188
181 def capabilities(self):
189 def capabilities(self):
182 if self._caps is None:
190 if self._caps is None:
183 try:
191 try:
184 self._fetchcaps()
192 self._fetchcaps()
185 except error.RepoError:
193 except error.RepoError:
186 self._caps = set()
194 self._caps = set()
187 self.ui.debug('capabilities: %s\n' %
195 self.ui.debug('capabilities: %s\n' %
188 (' '.join(self._caps or ['none'])))
196 (' '.join(self._caps or ['none'])))
189 return self._caps
197 return self._caps
190
198
191 # End of _basewirepeer interface.
199 # End of _basewirepeer interface.
192
200
193 # look up capabilities only when needed
201 # look up capabilities only when needed
194
202
195 def _fetchcaps(self):
203 def _fetchcaps(self):
196 self._caps = set(self._call('capabilities').split())
204 self._caps = set(self._call('capabilities').split())
197
205
198 def _callstream(self, cmd, _compressible=False, **args):
206 def _callstream(self, cmd, _compressible=False, **args):
199 if cmd == 'pushkey':
207 if cmd == 'pushkey':
200 args['data'] = ''
208 args['data'] = ''
201 data = args.pop('data', None)
209 data = args.pop('data', None)
202 headers = args.pop('headers', {})
210 headers = args.pop('headers', {})
203
211
204 self.ui.debug("sending %s command\n" % cmd)
212 self.ui.debug("sending %s command\n" % cmd)
205 q = [('cmd', cmd)]
213 q = [('cmd', cmd)]
206 headersize = 0
214 headersize = 0
207 varyheaders = []
215 varyheaders = []
208 # Important: don't use self.capable() here or else you end up
216 # Important: don't use self.capable() here or else you end up
209 # with infinite recursion when trying to look up capabilities
217 # with infinite recursion when trying to look up capabilities
210 # for the first time.
218 # for the first time.
211 postargsok = self._caps is not None and 'httppostargs' in self._caps
219 postargsok = self._caps is not None and 'httppostargs' in self._caps
212 if postargsok and args:
220 if postargsok and args:
213 strargs = urlreq.urlencode(sorted(args.items()))
221 strargs = urlreq.urlencode(sorted(args.items()))
214 if not data:
222 if not data:
215 data = strargs
223 data = strargs
216 else:
224 else:
217 if isinstance(data, basestring):
225 if isinstance(data, basestring):
218 i = io.BytesIO(data)
226 i = io.BytesIO(data)
219 i.length = len(data)
227 i.length = len(data)
220 data = i
228 data = i
221 argsio = io.BytesIO(strargs)
229 argsio = io.BytesIO(strargs)
222 argsio.length = len(strargs)
230 argsio.length = len(strargs)
223 data = _multifile(argsio, data)
231 data = _multifile(argsio, data)
224 headers[r'X-HgArgs-Post'] = len(strargs)
232 headers[r'X-HgArgs-Post'] = len(strargs)
225 else:
233 else:
226 if len(args) > 0:
234 if len(args) > 0:
227 httpheader = self.capable('httpheader')
235 httpheader = self.capable('httpheader')
228 if httpheader:
236 if httpheader:
229 headersize = int(httpheader.split(',', 1)[0])
237 headersize = int(httpheader.split(',', 1)[0])
230 if headersize > 0:
238 if headersize > 0:
231 # The headers can typically carry more data than the URL.
239 # The headers can typically carry more data than the URL.
232 encargs = urlreq.urlencode(sorted(args.items()))
240 encargs = urlreq.urlencode(sorted(args.items()))
233 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
241 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
234 headersize):
242 headersize):
235 headers[header] = value
243 headers[header] = value
236 varyheaders.append(header)
244 varyheaders.append(header)
237 else:
245 else:
238 q += sorted(args.items())
246 q += sorted(args.items())
239 qs = '?%s' % urlreq.urlencode(q)
247 qs = '?%s' % urlreq.urlencode(q)
240 cu = "%s%s" % (self._url, qs)
248 cu = "%s%s" % (self._url, qs)
241 size = 0
249 size = 0
242 if util.safehasattr(data, 'length'):
250 if util.safehasattr(data, 'length'):
243 size = data.length
251 size = data.length
244 elif data is not None:
252 elif data is not None:
245 size = len(data)
253 size = len(data)
246 if size and self.ui.configbool('ui', 'usehttp2'):
254 if size and self.ui.configbool('ui', 'usehttp2'):
247 headers[r'Expect'] = r'100-Continue'
255 headers[r'Expect'] = r'100-Continue'
248 headers[r'X-HgHttp2'] = r'1'
256 headers[r'X-HgHttp2'] = r'1'
249 if data is not None and r'Content-Type' not in headers:
257 if data is not None and r'Content-Type' not in headers:
250 headers[r'Content-Type'] = r'application/mercurial-0.1'
258 headers[r'Content-Type'] = r'application/mercurial-0.1'
251
259
252 # Tell the server we accept application/mercurial-0.2 and multiple
260 # Tell the server we accept application/mercurial-0.2 and multiple
253 # compression formats if the server is capable of emitting those
261 # compression formats if the server is capable of emitting those
254 # payloads.
262 # payloads.
255 protoparams = []
263 protoparams = []
256
264
257 mediatypes = set()
265 mediatypes = set()
258 if self._caps is not None:
266 if self._caps is not None:
259 mt = self.capable('httpmediatype')
267 mt = self.capable('httpmediatype')
260 if mt:
268 if mt:
261 protoparams.append('0.1')
269 protoparams.append('0.1')
262 mediatypes = set(mt.split(','))
270 mediatypes = set(mt.split(','))
263
271
264 if '0.2tx' in mediatypes:
272 if '0.2tx' in mediatypes:
265 protoparams.append('0.2')
273 protoparams.append('0.2')
266
274
267 if '0.2tx' in mediatypes and self.capable('compression'):
275 if '0.2tx' in mediatypes and self.capable('compression'):
268 # We /could/ compare supported compression formats and prune
276 # We /could/ compare supported compression formats and prune
269 # non-mutually supported or error if nothing is mutually supported.
277 # non-mutually supported or error if nothing is mutually supported.
270 # For now, send the full list to the server and have it error.
278 # For now, send the full list to the server and have it error.
271 comps = [e.wireprotosupport().name for e in
279 comps = [e.wireprotosupport().name for e in
272 util.compengines.supportedwireengines(util.CLIENTROLE)]
280 util.compengines.supportedwireengines(util.CLIENTROLE)]
273 protoparams.append('comp=%s' % ','.join(comps))
281 protoparams.append('comp=%s' % ','.join(comps))
274
282
275 if protoparams:
283 if protoparams:
276 protoheaders = encodevalueinheaders(' '.join(protoparams),
284 protoheaders = encodevalueinheaders(' '.join(protoparams),
277 'X-HgProto',
285 'X-HgProto',
278 headersize or 1024)
286 headersize or 1024)
279 for header, value in protoheaders:
287 for header, value in protoheaders:
280 headers[header] = value
288 headers[header] = value
281 varyheaders.append(header)
289 varyheaders.append(header)
282
290
283 if varyheaders:
291 if varyheaders:
284 headers[r'Vary'] = r','.join(varyheaders)
292 headers[r'Vary'] = r','.join(varyheaders)
285
293
286 req = self._requestbuilder(pycompat.strurl(cu), data, headers)
294 req = self._requestbuilder(pycompat.strurl(cu), data, headers)
287
295
288 if data is not None:
296 if data is not None:
289 self.ui.debug("sending %s bytes\n" % size)
297 self.ui.debug("sending %s bytes\n" % size)
290 req.add_unredirected_header('Content-Length', '%d' % size)
298 req.add_unredirected_header('Content-Length', '%d' % size)
291 try:
299 try:
292 resp = self._urlopener.open(req)
300 resp = self._urlopener.open(req)
293 except urlerr.httperror as inst:
301 except urlerr.httperror as inst:
294 if inst.code == 401:
302 if inst.code == 401:
295 raise error.Abort(_('authorization failed'))
303 raise error.Abort(_('authorization failed'))
296 raise
304 raise
297 except httplib.HTTPException as inst:
305 except httplib.HTTPException as inst:
298 self.ui.debug('http error while sending %s command\n' % cmd)
306 self.ui.debug('http error while sending %s command\n' % cmd)
299 self.ui.traceback()
307 self.ui.traceback()
300 raise IOError(None, inst)
308 raise IOError(None, inst)
301
309
302 # Insert error handlers for common I/O failures.
310 # Insert error handlers for common I/O failures.
303 _wraphttpresponse(resp)
311 _wraphttpresponse(resp)
304
312
305 # record the url we got redirected to
313 # record the url we got redirected to
306 resp_url = pycompat.bytesurl(resp.geturl())
314 resp_url = pycompat.bytesurl(resp.geturl())
307 if resp_url.endswith(qs):
315 if resp_url.endswith(qs):
308 resp_url = resp_url[:-len(qs)]
316 resp_url = resp_url[:-len(qs)]
309 if self._url.rstrip('/') != resp_url.rstrip('/'):
317 if self._url.rstrip('/') != resp_url.rstrip('/'):
310 if not self.ui.quiet:
318 if not self.ui.quiet:
311 self.ui.warn(_('real URL is %s\n') % resp_url)
319 self.ui.warn(_('real URL is %s\n') % resp_url)
312 self._url = resp_url
320 self._url = resp_url
313 try:
321 try:
314 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
322 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
315 except AttributeError:
323 except AttributeError:
316 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
324 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
317
325
318 safeurl = util.hidepassword(self._url)
326 safeurl = util.hidepassword(self._url)
319 if proto.startswith('application/hg-error'):
327 if proto.startswith('application/hg-error'):
320 raise error.OutOfBandError(resp.read())
328 raise error.OutOfBandError(resp.read())
321 # accept old "text/plain" and "application/hg-changegroup" for now
329 # accept old "text/plain" and "application/hg-changegroup" for now
322 if not (proto.startswith('application/mercurial-') or
330 if not (proto.startswith('application/mercurial-') or
323 (proto.startswith('text/plain')
331 (proto.startswith('text/plain')
324 and not resp.headers.get('content-length')) or
332 and not resp.headers.get('content-length')) or
325 proto.startswith('application/hg-changegroup')):
333 proto.startswith('application/hg-changegroup')):
326 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
334 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
327 raise error.RepoError(
335 raise error.RepoError(
328 _("'%s' does not appear to be an hg repository:\n"
336 _("'%s' does not appear to be an hg repository:\n"
329 "---%%<--- (%s)\n%s\n---%%<---\n")
337 "---%%<--- (%s)\n%s\n---%%<---\n")
330 % (safeurl, proto or 'no content-type', resp.read(1024)))
338 % (safeurl, proto or 'no content-type', resp.read(1024)))
331
339
332 if proto.startswith('application/mercurial-'):
340 if proto.startswith('application/mercurial-'):
333 try:
341 try:
334 version = proto.split('-', 1)[1]
342 version = proto.split('-', 1)[1]
335 version_info = tuple([int(n) for n in version.split('.')])
343 version_info = tuple([int(n) for n in version.split('.')])
336 except ValueError:
344 except ValueError:
337 raise error.RepoError(_("'%s' sent a broken Content-Type "
345 raise error.RepoError(_("'%s' sent a broken Content-Type "
338 "header (%s)") % (safeurl, proto))
346 "header (%s)") % (safeurl, proto))
339
347
340 # TODO consider switching to a decompression reader that uses
348 # TODO consider switching to a decompression reader that uses
341 # generators.
349 # generators.
342 if version_info == (0, 1):
350 if version_info == (0, 1):
343 if _compressible:
351 if _compressible:
344 return util.compengines['zlib'].decompressorreader(resp)
352 return util.compengines['zlib'].decompressorreader(resp)
345 return resp
353 return resp
346 elif version_info == (0, 2):
354 elif version_info == (0, 2):
347 # application/mercurial-0.2 always identifies the compression
355 # application/mercurial-0.2 always identifies the compression
348 # engine in the payload header.
356 # engine in the payload header.
349 elen = struct.unpack('B', resp.read(1))[0]
357 elen = struct.unpack('B', resp.read(1))[0]
350 ename = resp.read(elen)
358 ename = resp.read(elen)
351 engine = util.compengines.forwiretype(ename)
359 engine = util.compengines.forwiretype(ename)
352 return engine.decompressorreader(resp)
360 return engine.decompressorreader(resp)
353 else:
361 else:
354 raise error.RepoError(_("'%s' uses newer protocol %s") %
362 raise error.RepoError(_("'%s' uses newer protocol %s") %
355 (safeurl, version))
363 (safeurl, version))
356
364
357 if _compressible:
365 if _compressible:
358 return util.compengines['zlib'].decompressorreader(resp)
366 return util.compengines['zlib'].decompressorreader(resp)
359
367
360 return resp
368 return resp
361
369
362 def _call(self, cmd, **args):
370 def _call(self, cmd, **args):
363 fp = self._callstream(cmd, **args)
371 fp = self._callstream(cmd, **args)
364 try:
372 try:
365 return fp.read()
373 return fp.read()
366 finally:
374 finally:
367 # if using keepalive, allow connection to be reused
375 # if using keepalive, allow connection to be reused
368 fp.close()
376 fp.close()
369
377
370 def _callpush(self, cmd, cg, **args):
378 def _callpush(self, cmd, cg, **args):
371 # have to stream bundle to a temp file because we do not have
379 # have to stream bundle to a temp file because we do not have
372 # http 1.1 chunked transfer.
380 # http 1.1 chunked transfer.
373
381
374 types = self.capable('unbundle')
382 types = self.capable('unbundle')
375 try:
383 try:
376 types = types.split(',')
384 types = types.split(',')
377 except AttributeError:
385 except AttributeError:
378 # servers older than d1b16a746db6 will send 'unbundle' as a
386 # servers older than d1b16a746db6 will send 'unbundle' as a
379 # boolean capability. They only support headerless/uncompressed
387 # boolean capability. They only support headerless/uncompressed
380 # bundles.
388 # bundles.
381 types = [""]
389 types = [""]
382 for x in types:
390 for x in types:
383 if x in bundle2.bundletypes:
391 if x in bundle2.bundletypes:
384 type = x
392 type = x
385 break
393 break
386
394
387 tempname = bundle2.writebundle(self.ui, cg, None, type)
395 tempname = bundle2.writebundle(self.ui, cg, None, type)
388 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
396 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
389 headers = {'Content-Type': 'application/mercurial-0.1'}
397 headers = {'Content-Type': 'application/mercurial-0.1'}
390
398
391 try:
399 try:
392 r = self._call(cmd, data=fp, headers=headers, **args)
400 r = self._call(cmd, data=fp, headers=headers, **args)
393 vals = r.split('\n', 1)
401 vals = r.split('\n', 1)
394 if len(vals) < 2:
402 if len(vals) < 2:
395 raise error.ResponseError(_("unexpected response:"), r)
403 raise error.ResponseError(_("unexpected response:"), r)
396 return vals
404 return vals
397 except socket.error as err:
405 except socket.error as err:
398 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
406 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
399 raise error.Abort(_('push failed: %s') % err.args[1])
407 raise error.Abort(_('push failed: %s') % err.args[1])
400 raise error.Abort(err.args[1])
408 raise error.Abort(err.args[1])
401 finally:
409 finally:
402 fp.close()
410 fp.close()
403 os.unlink(tempname)
411 os.unlink(tempname)
404
412
405 def _calltwowaystream(self, cmd, fp, **args):
413 def _calltwowaystream(self, cmd, fp, **args):
406 fh = None
414 fh = None
407 fp_ = None
415 fp_ = None
408 filename = None
416 filename = None
409 try:
417 try:
410 # dump bundle to disk
418 # dump bundle to disk
411 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
419 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
412 fh = os.fdopen(fd, pycompat.sysstr("wb"))
420 fh = os.fdopen(fd, pycompat.sysstr("wb"))
413 d = fp.read(4096)
421 d = fp.read(4096)
414 while d:
422 while d:
415 fh.write(d)
423 fh.write(d)
416 d = fp.read(4096)
424 d = fp.read(4096)
417 fh.close()
425 fh.close()
418 # start http push
426 # start http push
419 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
427 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
420 headers = {'Content-Type': 'application/mercurial-0.1'}
428 headers = {'Content-Type': 'application/mercurial-0.1'}
421 return self._callstream(cmd, data=fp_, headers=headers, **args)
429 return self._callstream(cmd, data=fp_, headers=headers, **args)
422 finally:
430 finally:
423 if fp_ is not None:
431 if fp_ is not None:
424 fp_.close()
432 fp_.close()
425 if fh is not None:
433 if fh is not None:
426 fh.close()
434 fh.close()
427 os.unlink(filename)
435 os.unlink(filename)
428
436
429 def _callcompressable(self, cmd, **args):
437 def _callcompressable(self, cmd, **args):
430 return self._callstream(cmd, _compressible=True, **args)
438 return self._callstream(cmd, _compressible=True, **args)
431
439
432 def _abort(self, exception):
440 def _abort(self, exception):
433 raise exception
441 raise exception
434
442
435 class httpspeer(httppeer):
443 class httpspeer(httppeer):
436 def __init__(self, ui, path):
444 def __init__(self, ui, path):
437 if not url.has_https:
445 if not url.has_https:
438 raise error.Abort(_('Python support for SSL and HTTPS '
446 raise error.Abort(_('Python support for SSL and HTTPS '
439 'is not installed'))
447 'is not installed'))
440 httppeer.__init__(self, ui, path)
448 httppeer.__init__(self, ui, path)
441
449
442 def instance(ui, path, create):
450 def instance(ui, path, create):
443 if create:
451 if create:
444 raise error.Abort(_('cannot create new http repository'))
452 raise error.Abort(_('cannot create new http repository'))
445 try:
453 try:
446 if path.startswith('https:'):
454 if path.startswith('https:'):
447 inst = httpspeer(ui, path)
455 inst = httpspeer(ui, path)
448 else:
456 else:
449 inst = httppeer(ui, path)
457 inst = httppeer(ui, path)
450 try:
458 try:
451 # Try to do useful work when checking compatibility.
459 # Try to do useful work when checking compatibility.
452 # Usually saves a roundtrip since we want the caps anyway.
460 # Usually saves a roundtrip since we want the caps anyway.
453 inst._fetchcaps()
461 inst._fetchcaps()
454 except error.RepoError:
462 except error.RepoError:
455 # No luck, try older compatibility check.
463 # No luck, try older compatibility check.
456 inst.between([(nullid, nullid)])
464 inst.between([(nullid, nullid)])
457 return inst
465 return inst
458 except error.RepoError as httpexception:
466 except error.RepoError as httpexception:
459 try:
467 try:
460 r = statichttprepo.instance(ui, "static-" + path, create)
468 r = statichttprepo.instance(ui, "static-" + path, create)
461 ui.note(_('(falling back to static-http)\n'))
469 ui.note(_('(falling back to static-http)\n'))
462 return r
470 return r
463 except error.RepoError:
471 except error.RepoError:
464 raise httpexception # use the original http RepoError instead
472 raise httpexception # use the original http RepoError instead
General Comments 0
You need to be logged in to leave comments. Login now