##// END OF EJS Templates
httprepo: send Content-Type instead of content-type
Sune Foldager -
r10526:72d3a02c stable
parent child Browse files
Show More
@@ -1,274 +1,274 b''
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 bin, hex, nullid
9 from node import bin, hex, nullid
10 from i18n import _
10 from i18n import _
11 import repo, changegroup, statichttprepo, error, url, util
11 import repo, changegroup, statichttprepo, error, url, util
12 import os, urllib, urllib2, urlparse, zlib, httplib
12 import os, urllib, urllib2, urlparse, zlib, httplib
13 import errno, socket
13 import errno, socket
14 import encoding
14 import encoding
15
15
16 def zgenerator(f):
16 def zgenerator(f):
17 zd = zlib.decompressobj()
17 zd = zlib.decompressobj()
18 try:
18 try:
19 for chunk in util.filechunkiter(f):
19 for chunk in util.filechunkiter(f):
20 yield zd.decompress(chunk)
20 yield zd.decompress(chunk)
21 except httplib.HTTPException:
21 except httplib.HTTPException:
22 raise IOError(None, _('connection ended unexpectedly'))
22 raise IOError(None, _('connection ended unexpectedly'))
23 yield zd.flush()
23 yield zd.flush()
24
24
25 class httprepository(repo.repository):
25 class httprepository(repo.repository):
26 def __init__(self, ui, path):
26 def __init__(self, ui, path):
27 self.path = path
27 self.path = path
28 self.caps = None
28 self.caps = None
29 self.handler = None
29 self.handler = None
30 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
30 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
31 if query or frag:
31 if query or frag:
32 raise util.Abort(_('unsupported URL component: "%s"') %
32 raise util.Abort(_('unsupported URL component: "%s"') %
33 (query or frag))
33 (query or frag))
34
34
35 # urllib cannot handle URLs with embedded user or passwd
35 # urllib cannot handle URLs with embedded user or passwd
36 self._url, authinfo = url.getauthinfo(path)
36 self._url, authinfo = url.getauthinfo(path)
37
37
38 self.ui = ui
38 self.ui = ui
39 self.ui.debug('using %s\n' % self._url)
39 self.ui.debug('using %s\n' % self._url)
40
40
41 self.urlopener = url.opener(ui, authinfo)
41 self.urlopener = url.opener(ui, authinfo)
42
42
43 def __del__(self):
43 def __del__(self):
44 for h in self.urlopener.handlers:
44 for h in self.urlopener.handlers:
45 h.close()
45 h.close()
46 if hasattr(h, "close_all"):
46 if hasattr(h, "close_all"):
47 h.close_all()
47 h.close_all()
48
48
49 def url(self):
49 def url(self):
50 return self.path
50 return self.path
51
51
52 # look up capabilities only when needed
52 # look up capabilities only when needed
53
53
54 def get_caps(self):
54 def get_caps(self):
55 if self.caps is None:
55 if self.caps is None:
56 try:
56 try:
57 self.caps = set(self.do_read('capabilities').split())
57 self.caps = set(self.do_read('capabilities').split())
58 except error.RepoError:
58 except error.RepoError:
59 self.caps = set()
59 self.caps = set()
60 self.ui.debug('capabilities: %s\n' %
60 self.ui.debug('capabilities: %s\n' %
61 (' '.join(self.caps or ['none'])))
61 (' '.join(self.caps or ['none'])))
62 return self.caps
62 return self.caps
63
63
64 capabilities = property(get_caps)
64 capabilities = property(get_caps)
65
65
66 def lock(self):
66 def lock(self):
67 raise util.Abort(_('operation not supported over http'))
67 raise util.Abort(_('operation not supported over http'))
68
68
69 def do_cmd(self, cmd, **args):
69 def do_cmd(self, cmd, **args):
70 data = args.pop('data', None)
70 data = args.pop('data', None)
71 headers = args.pop('headers', {})
71 headers = args.pop('headers', {})
72 self.ui.debug("sending %s command\n" % cmd)
72 self.ui.debug("sending %s command\n" % cmd)
73 q = {"cmd": cmd}
73 q = {"cmd": cmd}
74 q.update(args)
74 q.update(args)
75 qs = '?%s' % urllib.urlencode(q)
75 qs = '?%s' % urllib.urlencode(q)
76 cu = "%s%s" % (self._url, qs)
76 cu = "%s%s" % (self._url, qs)
77 req = urllib2.Request(cu, data, headers)
77 req = urllib2.Request(cu, data, headers)
78 if data is not None:
78 if data is not None:
79 # len(data) is broken if data doesn't fit into Py_ssize_t
79 # len(data) is broken if data doesn't fit into Py_ssize_t
80 # add the header ourself to avoid OverflowError
80 # add the header ourself to avoid OverflowError
81 size = data.__len__()
81 size = data.__len__()
82 self.ui.debug("sending %s bytes\n" % size)
82 self.ui.debug("sending %s bytes\n" % size)
83 req.add_unredirected_header('Content-Length', '%d' % size)
83 req.add_unredirected_header('Content-Length', '%d' % size)
84 try:
84 try:
85 resp = self.urlopener.open(req)
85 resp = self.urlopener.open(req)
86 except urllib2.HTTPError, inst:
86 except urllib2.HTTPError, inst:
87 if inst.code == 401:
87 if inst.code == 401:
88 raise util.Abort(_('authorization failed'))
88 raise util.Abort(_('authorization failed'))
89 raise
89 raise
90 except httplib.HTTPException, inst:
90 except httplib.HTTPException, inst:
91 self.ui.debug('http error while sending %s command\n' % cmd)
91 self.ui.debug('http error while sending %s command\n' % cmd)
92 self.ui.traceback()
92 self.ui.traceback()
93 raise IOError(None, inst)
93 raise IOError(None, inst)
94 except IndexError:
94 except IndexError:
95 # this only happens with Python 2.3, later versions raise URLError
95 # this only happens with Python 2.3, later versions raise URLError
96 raise util.Abort(_('http error, possibly caused by proxy setting'))
96 raise util.Abort(_('http error, possibly caused by proxy setting'))
97 # record the url we got redirected to
97 # record the url we got redirected to
98 resp_url = resp.geturl()
98 resp_url = resp.geturl()
99 if resp_url.endswith(qs):
99 if resp_url.endswith(qs):
100 resp_url = resp_url[:-len(qs)]
100 resp_url = resp_url[:-len(qs)]
101 if self._url.rstrip('/') != resp_url.rstrip('/'):
101 if self._url.rstrip('/') != resp_url.rstrip('/'):
102 self.ui.status(_('real URL is %s\n') % resp_url)
102 self.ui.status(_('real URL is %s\n') % resp_url)
103 self._url = resp_url
103 self._url = resp_url
104 try:
104 try:
105 proto = resp.getheader('content-type')
105 proto = resp.getheader('content-type')
106 except AttributeError:
106 except AttributeError:
107 proto = resp.headers['content-type']
107 proto = resp.headers['content-type']
108
108
109 safeurl = url.hidepassword(self._url)
109 safeurl = url.hidepassword(self._url)
110 # accept old "text/plain" and "application/hg-changegroup" for now
110 # accept old "text/plain" and "application/hg-changegroup" for now
111 if not (proto.startswith('application/mercurial-') or
111 if not (proto.startswith('application/mercurial-') or
112 proto.startswith('text/plain') or
112 proto.startswith('text/plain') or
113 proto.startswith('application/hg-changegroup')):
113 proto.startswith('application/hg-changegroup')):
114 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
114 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
115 raise error.RepoError(
115 raise error.RepoError(
116 _("'%s' does not appear to be an hg repository:\n"
116 _("'%s' does not appear to be an hg repository:\n"
117 "---%%<--- (%s)\n%s\n---%%<---\n")
117 "---%%<--- (%s)\n%s\n---%%<---\n")
118 % (safeurl, proto, resp.read()))
118 % (safeurl, proto, resp.read()))
119
119
120 if proto.startswith('application/mercurial-'):
120 if proto.startswith('application/mercurial-'):
121 try:
121 try:
122 version = proto.split('-', 1)[1]
122 version = proto.split('-', 1)[1]
123 version_info = tuple([int(n) for n in version.split('.')])
123 version_info = tuple([int(n) for n in version.split('.')])
124 except ValueError:
124 except ValueError:
125 raise error.RepoError(_("'%s' sent a broken Content-Type "
125 raise error.RepoError(_("'%s' sent a broken Content-Type "
126 "header (%s)") % (safeurl, proto))
126 "header (%s)") % (safeurl, proto))
127 if version_info > (0, 1):
127 if version_info > (0, 1):
128 raise error.RepoError(_("'%s' uses newer protocol %s") %
128 raise error.RepoError(_("'%s' uses newer protocol %s") %
129 (safeurl, version))
129 (safeurl, version))
130
130
131 return resp
131 return resp
132
132
133 def do_read(self, cmd, **args):
133 def do_read(self, cmd, **args):
134 fp = self.do_cmd(cmd, **args)
134 fp = self.do_cmd(cmd, **args)
135 try:
135 try:
136 return fp.read()
136 return fp.read()
137 finally:
137 finally:
138 # if using keepalive, allow connection to be reused
138 # if using keepalive, allow connection to be reused
139 fp.close()
139 fp.close()
140
140
141 def lookup(self, key):
141 def lookup(self, key):
142 self.requirecap('lookup', _('look up remote revision'))
142 self.requirecap('lookup', _('look up remote revision'))
143 d = self.do_cmd("lookup", key = key).read()
143 d = self.do_cmd("lookup", key = key).read()
144 success, data = d[:-1].split(' ', 1)
144 success, data = d[:-1].split(' ', 1)
145 if int(success):
145 if int(success):
146 return bin(data)
146 return bin(data)
147 raise error.RepoError(data)
147 raise error.RepoError(data)
148
148
149 def heads(self):
149 def heads(self):
150 d = self.do_read("heads")
150 d = self.do_read("heads")
151 try:
151 try:
152 return map(bin, d[:-1].split(" "))
152 return map(bin, d[:-1].split(" "))
153 except:
153 except:
154 raise error.ResponseError(_("unexpected response:"), d)
154 raise error.ResponseError(_("unexpected response:"), d)
155
155
156 def branchmap(self):
156 def branchmap(self):
157 d = self.do_read("branchmap")
157 d = self.do_read("branchmap")
158 try:
158 try:
159 branchmap = {}
159 branchmap = {}
160 for branchpart in d.splitlines():
160 for branchpart in d.splitlines():
161 branchheads = branchpart.split(' ')
161 branchheads = branchpart.split(' ')
162 branchname = urllib.unquote(branchheads[0])
162 branchname = urllib.unquote(branchheads[0])
163 # Earlier servers (1.3.x) send branch names in (their) local
163 # Earlier servers (1.3.x) send branch names in (their) local
164 # charset. The best we can do is assume it's identical to our
164 # charset. The best we can do is assume it's identical to our
165 # own local charset, in case it's not utf-8.
165 # own local charset, in case it's not utf-8.
166 try:
166 try:
167 branchname.decode('utf-8')
167 branchname.decode('utf-8')
168 except UnicodeDecodeError:
168 except UnicodeDecodeError:
169 branchname = encoding.fromlocal(branchname)
169 branchname = encoding.fromlocal(branchname)
170 branchheads = [bin(x) for x in branchheads[1:]]
170 branchheads = [bin(x) for x in branchheads[1:]]
171 branchmap[branchname] = branchheads
171 branchmap[branchname] = branchheads
172 return branchmap
172 return branchmap
173 except:
173 except:
174 raise error.ResponseError(_("unexpected response:"), d)
174 raise error.ResponseError(_("unexpected response:"), d)
175
175
176 def branches(self, nodes):
176 def branches(self, nodes):
177 n = " ".join(map(hex, nodes))
177 n = " ".join(map(hex, nodes))
178 d = self.do_read("branches", nodes=n)
178 d = self.do_read("branches", nodes=n)
179 try:
179 try:
180 br = [tuple(map(bin, b.split(" "))) for b in d.splitlines()]
180 br = [tuple(map(bin, b.split(" "))) for b in d.splitlines()]
181 return br
181 return br
182 except:
182 except:
183 raise error.ResponseError(_("unexpected response:"), d)
183 raise error.ResponseError(_("unexpected response:"), d)
184
184
185 def between(self, pairs):
185 def between(self, pairs):
186 batch = 8 # avoid giant requests
186 batch = 8 # avoid giant requests
187 r = []
187 r = []
188 for i in xrange(0, len(pairs), batch):
188 for i in xrange(0, len(pairs), batch):
189 n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]])
189 n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]])
190 d = self.do_read("between", pairs=n)
190 d = self.do_read("between", pairs=n)
191 try:
191 try:
192 r += [l and map(bin, l.split(" ")) or []
192 r += [l and map(bin, l.split(" ")) or []
193 for l in d.splitlines()]
193 for l in d.splitlines()]
194 except:
194 except:
195 raise error.ResponseError(_("unexpected response:"), d)
195 raise error.ResponseError(_("unexpected response:"), d)
196 return r
196 return r
197
197
198 def changegroup(self, nodes, kind):
198 def changegroup(self, nodes, kind):
199 n = " ".join(map(hex, nodes))
199 n = " ".join(map(hex, nodes))
200 f = self.do_cmd("changegroup", roots=n)
200 f = self.do_cmd("changegroup", roots=n)
201 return util.chunkbuffer(zgenerator(f))
201 return util.chunkbuffer(zgenerator(f))
202
202
203 def changegroupsubset(self, bases, heads, source):
203 def changegroupsubset(self, bases, heads, source):
204 self.requirecap('changegroupsubset', _('look up remote changes'))
204 self.requirecap('changegroupsubset', _('look up remote changes'))
205 baselst = " ".join([hex(n) for n in bases])
205 baselst = " ".join([hex(n) for n in bases])
206 headlst = " ".join([hex(n) for n in heads])
206 headlst = " ".join([hex(n) for n in heads])
207 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
207 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
208 return util.chunkbuffer(zgenerator(f))
208 return util.chunkbuffer(zgenerator(f))
209
209
210 def unbundle(self, cg, heads, source):
210 def unbundle(self, cg, heads, source):
211 # have to stream bundle to a temp file because we do not have
211 # have to stream bundle to a temp file because we do not have
212 # http 1.1 chunked transfer.
212 # http 1.1 chunked transfer.
213
213
214 type = ""
214 type = ""
215 types = self.capable('unbundle')
215 types = self.capable('unbundle')
216 # servers older than d1b16a746db6 will send 'unbundle' as a
216 # servers older than d1b16a746db6 will send 'unbundle' as a
217 # boolean capability
217 # boolean capability
218 try:
218 try:
219 types = types.split(',')
219 types = types.split(',')
220 except AttributeError:
220 except AttributeError:
221 types = [""]
221 types = [""]
222 if types:
222 if types:
223 for x in types:
223 for x in types:
224 if x in changegroup.bundletypes:
224 if x in changegroup.bundletypes:
225 type = x
225 type = x
226 break
226 break
227
227
228 tempname = changegroup.writebundle(cg, None, type)
228 tempname = changegroup.writebundle(cg, None, type)
229 fp = url.httpsendfile(tempname, "rb")
229 fp = url.httpsendfile(tempname, "rb")
230 try:
230 try:
231 try:
231 try:
232 resp = self.do_read(
232 resp = self.do_read(
233 'unbundle', data=fp,
233 'unbundle', data=fp,
234 headers={'content-type': 'application/mercurial-0.1'},
234 headers={'Content-Type': 'application/mercurial-0.1'},
235 heads=' '.join(map(hex, heads)))
235 heads=' '.join(map(hex, heads)))
236 resp_code, output = resp.split('\n', 1)
236 resp_code, output = resp.split('\n', 1)
237 try:
237 try:
238 ret = int(resp_code)
238 ret = int(resp_code)
239 except ValueError, err:
239 except ValueError, err:
240 raise error.ResponseError(
240 raise error.ResponseError(
241 _('push failed (unexpected response):'), resp)
241 _('push failed (unexpected response):'), resp)
242 self.ui.write(output)
242 self.ui.write(output)
243 return ret
243 return ret
244 except socket.error, err:
244 except socket.error, err:
245 if err[0] in (errno.ECONNRESET, errno.EPIPE):
245 if err[0] in (errno.ECONNRESET, errno.EPIPE):
246 raise util.Abort(_('push failed: %s') % err[1])
246 raise util.Abort(_('push failed: %s') % err[1])
247 raise util.Abort(err[1])
247 raise util.Abort(err[1])
248 finally:
248 finally:
249 fp.close()
249 fp.close()
250 os.unlink(tempname)
250 os.unlink(tempname)
251
251
252 def stream_out(self):
252 def stream_out(self):
253 return self.do_cmd('stream_out')
253 return self.do_cmd('stream_out')
254
254
255 class httpsrepository(httprepository):
255 class httpsrepository(httprepository):
256 def __init__(self, ui, path):
256 def __init__(self, ui, path):
257 if not url.has_https:
257 if not url.has_https:
258 raise util.Abort(_('Python support for SSL and HTTPS '
258 raise util.Abort(_('Python support for SSL and HTTPS '
259 'is not installed'))
259 'is not installed'))
260 httprepository.__init__(self, ui, path)
260 httprepository.__init__(self, ui, path)
261
261
262 def instance(ui, path, create):
262 def instance(ui, path, create):
263 if create:
263 if create:
264 raise util.Abort(_('cannot create new http repository'))
264 raise util.Abort(_('cannot create new http repository'))
265 try:
265 try:
266 if path.startswith('https:'):
266 if path.startswith('https:'):
267 inst = httpsrepository(ui, path)
267 inst = httpsrepository(ui, path)
268 else:
268 else:
269 inst = httprepository(ui, path)
269 inst = httprepository(ui, path)
270 inst.between([(nullid, nullid)])
270 inst.between([(nullid, nullid)])
271 return inst
271 return inst
272 except error.RepoError:
272 except error.RepoError:
273 ui.note('(falling back to static-http)\n')
273 ui.note('(falling back to static-http)\n')
274 return statichttprepo.instance(ui, "static-" + path, create)
274 return statichttprepo.instance(ui, "static-" + path, create)
General Comments 0
You need to be logged in to leave comments. Login now