##// END OF EJS Templates
httppeer: indent existing argument handling with if True...
Augie Fackler -
r28485:d3893900 default
parent child Browse files
Show More
@@ -1,290 +1,292 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 httplib
12 import httplib
13 import os
13 import os
14 import socket
14 import socket
15 import tempfile
15 import tempfile
16 import urllib
16 import urllib
17 import urllib2
17 import urllib2
18 import zlib
18 import zlib
19
19
20 from .i18n import _
20 from .i18n import _
21 from .node import nullid
21 from .node import nullid
22 from . import (
22 from . import (
23 changegroup,
23 changegroup,
24 error,
24 error,
25 httpconnection,
25 httpconnection,
26 statichttprepo,
26 statichttprepo,
27 url,
27 url,
28 util,
28 util,
29 wireproto,
29 wireproto,
30 )
30 )
31
31
32 def zgenerator(f):
32 def zgenerator(f):
33 zd = zlib.decompressobj()
33 zd = zlib.decompressobj()
34 try:
34 try:
35 for chunk in util.filechunkiter(f):
35 for chunk in util.filechunkiter(f):
36 while chunk:
36 while chunk:
37 yield zd.decompress(chunk, 2**18)
37 yield zd.decompress(chunk, 2**18)
38 chunk = zd.unconsumed_tail
38 chunk = zd.unconsumed_tail
39 except httplib.HTTPException:
39 except httplib.HTTPException:
40 raise IOError(None, _('connection ended unexpectedly'))
40 raise IOError(None, _('connection ended unexpectedly'))
41 yield zd.flush()
41 yield zd.flush()
42
42
43 class httppeer(wireproto.wirepeer):
43 class httppeer(wireproto.wirepeer):
44 def __init__(self, ui, path):
44 def __init__(self, ui, path):
45 self.path = path
45 self.path = path
46 self.caps = None
46 self.caps = None
47 self.handler = None
47 self.handler = None
48 self.urlopener = None
48 self.urlopener = None
49 self.requestbuilder = None
49 self.requestbuilder = None
50 u = util.url(path)
50 u = util.url(path)
51 if u.query or u.fragment:
51 if u.query or u.fragment:
52 raise error.Abort(_('unsupported URL component: "%s"') %
52 raise error.Abort(_('unsupported URL component: "%s"') %
53 (u.query or u.fragment))
53 (u.query or u.fragment))
54
54
55 # urllib cannot handle URLs with embedded user or passwd
55 # urllib cannot handle URLs with embedded user or passwd
56 self._url, authinfo = u.authinfo()
56 self._url, authinfo = u.authinfo()
57
57
58 self.ui = ui
58 self.ui = ui
59 self.ui.debug('using %s\n' % self._url)
59 self.ui.debug('using %s\n' % self._url)
60
60
61 self.urlopener = url.opener(ui, authinfo)
61 self.urlopener = url.opener(ui, authinfo)
62 self.requestbuilder = urllib2.Request
62 self.requestbuilder = urllib2.Request
63
63
64 def __del__(self):
64 def __del__(self):
65 if self.urlopener:
65 if self.urlopener:
66 for h in self.urlopener.handlers:
66 for h in self.urlopener.handlers:
67 h.close()
67 h.close()
68 getattr(h, "close_all", lambda : None)()
68 getattr(h, "close_all", lambda : None)()
69
69
70 def url(self):
70 def url(self):
71 return self.path
71 return self.path
72
72
73 # look up capabilities only when needed
73 # look up capabilities only when needed
74
74
75 def _fetchcaps(self):
75 def _fetchcaps(self):
76 self.caps = set(self._call('capabilities').split())
76 self.caps = set(self._call('capabilities').split())
77
77
78 def _capabilities(self):
78 def _capabilities(self):
79 if self.caps is None:
79 if self.caps is None:
80 try:
80 try:
81 self._fetchcaps()
81 self._fetchcaps()
82 except error.RepoError:
82 except error.RepoError:
83 self.caps = set()
83 self.caps = set()
84 self.ui.debug('capabilities: %s\n' %
84 self.ui.debug('capabilities: %s\n' %
85 (' '.join(self.caps or ['none'])))
85 (' '.join(self.caps or ['none'])))
86 return self.caps
86 return self.caps
87
87
88 def lock(self):
88 def lock(self):
89 raise error.Abort(_('operation not supported over http'))
89 raise error.Abort(_('operation not supported over http'))
90
90
91 def _callstream(self, cmd, **args):
91 def _callstream(self, cmd, **args):
92 if cmd == 'pushkey':
92 if cmd == 'pushkey':
93 args['data'] = ''
93 args['data'] = ''
94 data = args.pop('data', None)
94 data = args.pop('data', None)
95 headers = args.pop('headers', {})
95 headers = args.pop('headers', {})
96
96
97 self.ui.debug("sending %s command\n" % cmd)
97 self.ui.debug("sending %s command\n" % cmd)
98 q = [('cmd', cmd)]
98 q = [('cmd', cmd)]
99 headersize = 0
99 headersize = 0
100 if len(args) > 0:
100 if True:
101 httpheader = self.capable('httpheader')
101 if len(args) > 0:
102 if httpheader:
102 httpheader = self.capable('httpheader')
103 headersize = int(httpheader.split(',', 1)[0])
103 if httpheader:
104 if headersize > 0:
104 headersize = int(httpheader.split(',', 1)[0])
105 # The headers can typically carry more data than the URL.
105 if headersize > 0:
106 encargs = urllib.urlencode(sorted(args.items()))
106 # The headers can typically carry more data than the URL.
107 headerfmt = 'X-HgArg-%s'
107 encargs = urllib.urlencode(sorted(args.items()))
108 contentlen = headersize - len(headerfmt % '000' + ': \r\n')
108 headerfmt = 'X-HgArg-%s'
109 headernum = 0
109 contentlen = headersize - len(headerfmt % '000' + ': \r\n')
110 for i in xrange(0, len(encargs), contentlen):
110 headernum = 0
111 headernum += 1
111 for i in xrange(0, len(encargs), contentlen):
112 header = headerfmt % str(headernum)
112 headernum += 1
113 headers[header] = encargs[i:i + contentlen]
113 header = headerfmt % str(headernum)
114 varyheaders = [headerfmt % str(h) for h in range(1, headernum + 1)]
114 headers[header] = encargs[i:i + contentlen]
115 headers['Vary'] = ','.join(varyheaders)
115 varyheaders = [
116 else:
116 headerfmt % str(h) for h in range(1, headernum + 1)]
117 q += sorted(args.items())
117 headers['Vary'] = ','.join(varyheaders)
118 else:
119 q += sorted(args.items())
118 qs = '?%s' % urllib.urlencode(q)
120 qs = '?%s' % urllib.urlencode(q)
119 cu = "%s%s" % (self._url, qs)
121 cu = "%s%s" % (self._url, qs)
120 size = 0
122 size = 0
121 if util.safehasattr(data, 'length'):
123 if util.safehasattr(data, 'length'):
122 size = data.length
124 size = data.length
123 elif data is not None:
125 elif data is not None:
124 size = len(data)
126 size = len(data)
125 if size and self.ui.configbool('ui', 'usehttp2', False):
127 if size and self.ui.configbool('ui', 'usehttp2', False):
126 headers['Expect'] = '100-Continue'
128 headers['Expect'] = '100-Continue'
127 headers['X-HgHttp2'] = '1'
129 headers['X-HgHttp2'] = '1'
128 if data is not None and 'Content-Type' not in headers:
130 if data is not None and 'Content-Type' not in headers:
129 headers['Content-Type'] = 'application/mercurial-0.1'
131 headers['Content-Type'] = 'application/mercurial-0.1'
130 req = self.requestbuilder(cu, data, headers)
132 req = self.requestbuilder(cu, data, headers)
131 if data is not None:
133 if data is not None:
132 self.ui.debug("sending %s bytes\n" % size)
134 self.ui.debug("sending %s bytes\n" % size)
133 req.add_unredirected_header('Content-Length', '%d' % size)
135 req.add_unredirected_header('Content-Length', '%d' % size)
134 try:
136 try:
135 resp = self.urlopener.open(req)
137 resp = self.urlopener.open(req)
136 except urllib2.HTTPError as inst:
138 except urllib2.HTTPError as inst:
137 if inst.code == 401:
139 if inst.code == 401:
138 raise error.Abort(_('authorization failed'))
140 raise error.Abort(_('authorization failed'))
139 raise
141 raise
140 except httplib.HTTPException as inst:
142 except httplib.HTTPException as inst:
141 self.ui.debug('http error while sending %s command\n' % cmd)
143 self.ui.debug('http error while sending %s command\n' % cmd)
142 self.ui.traceback()
144 self.ui.traceback()
143 raise IOError(None, inst)
145 raise IOError(None, inst)
144 except IndexError:
146 except IndexError:
145 # this only happens with Python 2.3, later versions raise URLError
147 # this only happens with Python 2.3, later versions raise URLError
146 raise error.Abort(_('http error, possibly caused by proxy setting'))
148 raise error.Abort(_('http error, possibly caused by proxy setting'))
147 # record the url we got redirected to
149 # record the url we got redirected to
148 resp_url = resp.geturl()
150 resp_url = resp.geturl()
149 if resp_url.endswith(qs):
151 if resp_url.endswith(qs):
150 resp_url = resp_url[:-len(qs)]
152 resp_url = resp_url[:-len(qs)]
151 if self._url.rstrip('/') != resp_url.rstrip('/'):
153 if self._url.rstrip('/') != resp_url.rstrip('/'):
152 if not self.ui.quiet:
154 if not self.ui.quiet:
153 self.ui.warn(_('real URL is %s\n') % resp_url)
155 self.ui.warn(_('real URL is %s\n') % resp_url)
154 self._url = resp_url
156 self._url = resp_url
155 try:
157 try:
156 proto = resp.getheader('content-type')
158 proto = resp.getheader('content-type')
157 except AttributeError:
159 except AttributeError:
158 proto = resp.headers.get('content-type', '')
160 proto = resp.headers.get('content-type', '')
159
161
160 safeurl = util.hidepassword(self._url)
162 safeurl = util.hidepassword(self._url)
161 if proto.startswith('application/hg-error'):
163 if proto.startswith('application/hg-error'):
162 raise error.OutOfBandError(resp.read())
164 raise error.OutOfBandError(resp.read())
163 # accept old "text/plain" and "application/hg-changegroup" for now
165 # accept old "text/plain" and "application/hg-changegroup" for now
164 if not (proto.startswith('application/mercurial-') or
166 if not (proto.startswith('application/mercurial-') or
165 (proto.startswith('text/plain')
167 (proto.startswith('text/plain')
166 and not resp.headers.get('content-length')) or
168 and not resp.headers.get('content-length')) or
167 proto.startswith('application/hg-changegroup')):
169 proto.startswith('application/hg-changegroup')):
168 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
170 self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
169 raise error.RepoError(
171 raise error.RepoError(
170 _("'%s' does not appear to be an hg repository:\n"
172 _("'%s' does not appear to be an hg repository:\n"
171 "---%%<--- (%s)\n%s\n---%%<---\n")
173 "---%%<--- (%s)\n%s\n---%%<---\n")
172 % (safeurl, proto or 'no content-type', resp.read(1024)))
174 % (safeurl, proto or 'no content-type', resp.read(1024)))
173
175
174 if proto.startswith('application/mercurial-'):
176 if proto.startswith('application/mercurial-'):
175 try:
177 try:
176 version = proto.split('-', 1)[1]
178 version = proto.split('-', 1)[1]
177 version_info = tuple([int(n) for n in version.split('.')])
179 version_info = tuple([int(n) for n in version.split('.')])
178 except ValueError:
180 except ValueError:
179 raise error.RepoError(_("'%s' sent a broken Content-Type "
181 raise error.RepoError(_("'%s' sent a broken Content-Type "
180 "header (%s)") % (safeurl, proto))
182 "header (%s)") % (safeurl, proto))
181 if version_info > (0, 1):
183 if version_info > (0, 1):
182 raise error.RepoError(_("'%s' uses newer protocol %s") %
184 raise error.RepoError(_("'%s' uses newer protocol %s") %
183 (safeurl, version))
185 (safeurl, version))
184
186
185 return resp
187 return resp
186
188
187 def _call(self, cmd, **args):
189 def _call(self, cmd, **args):
188 fp = self._callstream(cmd, **args)
190 fp = self._callstream(cmd, **args)
189 try:
191 try:
190 return fp.read()
192 return fp.read()
191 finally:
193 finally:
192 # if using keepalive, allow connection to be reused
194 # if using keepalive, allow connection to be reused
193 fp.close()
195 fp.close()
194
196
195 def _callpush(self, cmd, cg, **args):
197 def _callpush(self, cmd, cg, **args):
196 # have to stream bundle to a temp file because we do not have
198 # have to stream bundle to a temp file because we do not have
197 # http 1.1 chunked transfer.
199 # http 1.1 chunked transfer.
198
200
199 types = self.capable('unbundle')
201 types = self.capable('unbundle')
200 try:
202 try:
201 types = types.split(',')
203 types = types.split(',')
202 except AttributeError:
204 except AttributeError:
203 # servers older than d1b16a746db6 will send 'unbundle' as a
205 # servers older than d1b16a746db6 will send 'unbundle' as a
204 # boolean capability. They only support headerless/uncompressed
206 # boolean capability. They only support headerless/uncompressed
205 # bundles.
207 # bundles.
206 types = [""]
208 types = [""]
207 for x in types:
209 for x in types:
208 if x in changegroup.bundletypes:
210 if x in changegroup.bundletypes:
209 type = x
211 type = x
210 break
212 break
211
213
212 tempname = changegroup.writebundle(self.ui, cg, None, type)
214 tempname = changegroup.writebundle(self.ui, cg, None, type)
213 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
215 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
214 headers = {'Content-Type': 'application/mercurial-0.1'}
216 headers = {'Content-Type': 'application/mercurial-0.1'}
215
217
216 try:
218 try:
217 r = self._call(cmd, data=fp, headers=headers, **args)
219 r = self._call(cmd, data=fp, headers=headers, **args)
218 vals = r.split('\n', 1)
220 vals = r.split('\n', 1)
219 if len(vals) < 2:
221 if len(vals) < 2:
220 raise error.ResponseError(_("unexpected response:"), r)
222 raise error.ResponseError(_("unexpected response:"), r)
221 return vals
223 return vals
222 except socket.error as err:
224 except socket.error as err:
223 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
225 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
224 raise error.Abort(_('push failed: %s') % err.args[1])
226 raise error.Abort(_('push failed: %s') % err.args[1])
225 raise error.Abort(err.args[1])
227 raise error.Abort(err.args[1])
226 finally:
228 finally:
227 fp.close()
229 fp.close()
228 os.unlink(tempname)
230 os.unlink(tempname)
229
231
230 def _calltwowaystream(self, cmd, fp, **args):
232 def _calltwowaystream(self, cmd, fp, **args):
231 fh = None
233 fh = None
232 fp_ = None
234 fp_ = None
233 filename = None
235 filename = None
234 try:
236 try:
235 # dump bundle to disk
237 # dump bundle to disk
236 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
238 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
237 fh = os.fdopen(fd, "wb")
239 fh = os.fdopen(fd, "wb")
238 d = fp.read(4096)
240 d = fp.read(4096)
239 while d:
241 while d:
240 fh.write(d)
242 fh.write(d)
241 d = fp.read(4096)
243 d = fp.read(4096)
242 fh.close()
244 fh.close()
243 # start http push
245 # start http push
244 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
246 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
245 headers = {'Content-Type': 'application/mercurial-0.1'}
247 headers = {'Content-Type': 'application/mercurial-0.1'}
246 return self._callstream(cmd, data=fp_, headers=headers, **args)
248 return self._callstream(cmd, data=fp_, headers=headers, **args)
247 finally:
249 finally:
248 if fp_ is not None:
250 if fp_ is not None:
249 fp_.close()
251 fp_.close()
250 if fh is not None:
252 if fh is not None:
251 fh.close()
253 fh.close()
252 os.unlink(filename)
254 os.unlink(filename)
253
255
254 def _callcompressable(self, cmd, **args):
256 def _callcompressable(self, cmd, **args):
255 stream = self._callstream(cmd, **args)
257 stream = self._callstream(cmd, **args)
256 return util.chunkbuffer(zgenerator(stream))
258 return util.chunkbuffer(zgenerator(stream))
257
259
258 def _abort(self, exception):
260 def _abort(self, exception):
259 raise exception
261 raise exception
260
262
261 class httpspeer(httppeer):
263 class httpspeer(httppeer):
262 def __init__(self, ui, path):
264 def __init__(self, ui, path):
263 if not url.has_https:
265 if not url.has_https:
264 raise error.Abort(_('Python support for SSL and HTTPS '
266 raise error.Abort(_('Python support for SSL and HTTPS '
265 'is not installed'))
267 'is not installed'))
266 httppeer.__init__(self, ui, path)
268 httppeer.__init__(self, ui, path)
267
269
268 def instance(ui, path, create):
270 def instance(ui, path, create):
269 if create:
271 if create:
270 raise error.Abort(_('cannot create new http repository'))
272 raise error.Abort(_('cannot create new http repository'))
271 try:
273 try:
272 if path.startswith('https:'):
274 if path.startswith('https:'):
273 inst = httpspeer(ui, path)
275 inst = httpspeer(ui, path)
274 else:
276 else:
275 inst = httppeer(ui, path)
277 inst = httppeer(ui, path)
276 try:
278 try:
277 # Try to do useful work when checking compatibility.
279 # Try to do useful work when checking compatibility.
278 # Usually saves a roundtrip since we want the caps anyway.
280 # Usually saves a roundtrip since we want the caps anyway.
279 inst._fetchcaps()
281 inst._fetchcaps()
280 except error.RepoError:
282 except error.RepoError:
281 # No luck, try older compatibility check.
283 # No luck, try older compatibility check.
282 inst.between([(nullid, nullid)])
284 inst.between([(nullid, nullid)])
283 return inst
285 return inst
284 except error.RepoError as httpexception:
286 except error.RepoError as httpexception:
285 try:
287 try:
286 r = statichttprepo.instance(ui, "static-" + path, create)
288 r = statichttprepo.instance(ui, "static-" + path, create)
287 ui.note('(falling back to static-http)\n')
289 ui.note('(falling back to static-http)\n')
288 return r
290 return r
289 except error.RepoError:
291 except error.RepoError:
290 raise httpexception # use the original http RepoError instead
292 raise httpexception # use the original http RepoError instead
General Comments 0
You need to be logged in to leave comments. Login now