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