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