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