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