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