/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3059.2.2 by Vincent Ladeuil
Read http responses on demand without buffering the whole body
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
1540.3.18 by Martin Pool
Style review fixes (thanks robertc)
2
#
1185.11.19 by John Arbash Meinel
Testing put and append, also testing agaist file-like objects as well as strings.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1540.3.18 by Martin Pool
Style review fixes (thanks robertc)
7
#
1185.11.19 by John Arbash Meinel
Testing put and append, also testing agaist file-like objects as well as strings.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1540.3.18 by Martin Pool
Style review fixes (thanks robertc)
12
#
1185.11.19 by John Arbash Meinel
Testing put and append, also testing agaist file-like objects as well as strings.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
1540.3.3 by Martin Pool
Review updates of pycurl transport
16
17
"""Base implementation of Transport over http.
18
19
There are separate implementation modules for each http client implementation.
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
20
"""
21
1711.4.14 by John Arbash Meinel
Custom HttpRequestHandler which treats all paths as utf8 encoded
22
from cStringIO import StringIO
1786.1.25 by John Arbash Meinel
Test that we can extract headers properly.
23
import mimetools
1540.3.23 by Martin Pool
Allow urls like http+pycurl://host/ to use a particular impl
24
import re
1540.3.3 by Martin Pool
Review updates of pycurl transport
25
import urlparse
26
import urllib
2172.3.2 by v.ladeuil+lp at free
Fix the missing import and typos in comments.
27
import sys
1786.1.6 by John Arbash Meinel
Missed a couple of imports
28
2485.8.24 by Vincent Ladeuil
Finish http refactoring. Test suite passing.
29
from bzrlib import (
30
    errors,
31
    ui,
32
    urlutils,
33
    )
2400.1.3 by Andrew Bennetts
Split smart transport code into several separate modules.
34
from bzrlib.smart import medium
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
35
from bzrlib.symbol_versioning import (
36
        deprecated_method,
37
        zero_seventeen,
38
        )
1185.11.1 by John Arbash Meinel
(broken) Transport work is merged in. Tests do not pass yet.
39
from bzrlib.trace import mutter
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
40
from bzrlib.transport import (
2485.8.16 by Vincent Ladeuil
Create a new, empty, ConnectedTransport class.
41
    ConnectedTransport,
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
42
    _CoalescedOffset,
2018.2.2 by Andrew Bennetts
Implement HTTP smart server.
43
    Transport,
44
    )
1540.3.6 by Martin Pool
[merge] update from bzr.dev
45
2004.1.9 by vila
Takes jam's remarks into account when possible, add TODOs for the rest.
46
# TODO: This is not used anymore by HttpTransport_urllib
47
# (extracting the auth info and prompting the user for a password
48
# have been split), only the tests still use it. It should be
49
# deleted and the tests rewritten ASAP to stay in sync.
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
50
def extract_auth(url, password_manager):
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
51
    """Extract auth parameters from am HTTP/HTTPS url and add them to the given
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
52
    password manager.  Return the url, minus those auth parameters (which
53
    confuse urllib2).
54
    """
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
55
    assert re.match(r'^(https?)(\+\w+)?://', url), \
56
            'invalid absolute url %r' % url
1540.2.1 by Röbey Pointer
change http url parsing to use urlparse, and use the ui_factory to ask for a password if necessary
57
    scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
2004.3.1 by vila
Test ConnectionError exceptions.
58
1540.2.1 by Röbey Pointer
change http url parsing to use urlparse, and use the ui_factory to ask for a password if necessary
59
    if '@' in netloc:
60
        auth, netloc = netloc.split('@', 1)
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
61
        if ':' in auth:
62
            username, password = auth.split(':', 1)
63
        else:
64
            username, password = auth, None
1540.2.1 by Röbey Pointer
change http url parsing to use urlparse, and use the ui_factory to ask for a password if necessary
65
        if ':' in netloc:
66
            host = netloc.split(':', 1)[0]
67
        else:
68
            host = netloc
69
        username = urllib.unquote(username)
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
70
        if password is not None:
71
            password = urllib.unquote(password)
1540.2.1 by Röbey Pointer
change http url parsing to use urlparse, and use the ui_factory to ask for a password if necessary
72
        else:
2094.3.6 by John Arbash Meinel
[merge] bzr.dev 2158
73
            password = ui.ui_factory.get_password(
2004.2.1 by John Arbash Meinel
Cleanup of urllib functions
74
                prompt='HTTP %(user)s@%(host)s password',
75
                user=username, host=host)
1540.2.1 by Röbey Pointer
change http url parsing to use urlparse, and use the ui_factory to ask for a password if necessary
76
        password_manager.add_password(None, host, username, password)
77
    url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
78
    return url
1553.1.5 by James Henstridge
Make HTTP transport has() method do HEAD requests, and update test to
79
1185.50.83 by John Arbash Meinel
[merge] James Henstridge: Set Agent string in http headers, add tests for it.
80
2485.8.16 by Vincent Ladeuil
Create a new, empty, ConnectedTransport class.
81
class HttpTransportBase(ConnectedTransport, medium.SmartClientMedium):
1540.3.1 by Martin Pool
First-cut implementation of pycurl. Substantially faster than using urllib.
82
    """Base class for http implementations.
83
1540.3.23 by Martin Pool
Allow urls like http+pycurl://host/ to use a particular impl
84
    Does URL parsing, etc, but not any network IO.
85
86
    The protocol can be given as e.g. http+urllib://host/ to use a particular
87
    implementation.
88
    """
89
2485.8.24 by Vincent Ladeuil
Finish http refactoring. Test suite passing.
90
    # _unqualified_scheme: "http" or "https"
91
    # _scheme: may have "+pycurl", etc
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
92
2485.8.59 by Vincent Ladeuil
Update from review comments.
93
    def __init__(self, base, _from_transport=None):
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
94
        """Set the base path where files will be stored."""
1540.3.23 by Martin Pool
Allow urls like http+pycurl://host/ to use a particular impl
95
        proto_match = re.match(r'^(https?)(\+\w+)?://', base)
96
        if not proto_match:
97
            raise AssertionError("not a http url: %r" % base)
2485.8.24 by Vincent Ladeuil
Finish http refactoring. Test suite passing.
98
        self._unqualified_scheme = proto_match.group(1)
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
99
        impl_name = proto_match.group(2)
1540.3.23 by Martin Pool
Allow urls like http+pycurl://host/ to use a particular impl
100
        if impl_name:
101
            impl_name = impl_name[1:]
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
102
        self._impl_name = impl_name
2485.8.59 by Vincent Ladeuil
Update from review comments.
103
        super(HttpTransportBase, self).__init__(base,
104
                                                _from_transport=_from_transport)
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
105
        # range hint is handled dynamically throughout the life
2363.4.9 by Vincent Ladeuil
Catch first succesful authentification to avoid further 401
106
        # of the transport object. We start by trying multi-range
107
        # requests and if the server returns bogus results, we
108
        # retry with single range requests and, finally, we
109
        # forget about range if the server really can't
110
        # understand. Once acquired, this piece of info is
111
        # propagated to clones.
2485.8.59 by Vincent Ladeuil
Update from review comments.
112
        if _from_transport is not None:
113
            self._range_hint = _from_transport._range_hint
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
114
        else:
115
            self._range_hint = 'multi'
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
116
117
    def has(self, relpath):
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
118
        raise NotImplementedError("has() is abstract on %r" % self)
119
2164.2.15 by Vincent Ladeuil
Http redirections are not followed by default. Do not use hints
120
    def get(self, relpath):
1594.2.5 by Robert Collins
Readv patch from Johan Rydberg giving knits partial download support.
121
        """Get the file at the given relative path.
122
123
        :param relpath: The relative path to the file
124
        """
2164.2.15 by Vincent Ladeuil
Http redirections are not followed by default. Do not use hints
125
        code, response_file = self._get(relpath, None)
3059.2.2 by Vincent Ladeuil
Read http responses on demand without buffering the whole body
126
        # FIXME: some callers want an iterable... One step forward, three steps
3059.2.6 by Vincent Ladeuil
Light modifications after a failed attempt at making RangeFile iterable.
127
        # backwards :-/ And not only an iterable, but an iterable that can be
128
        # seeked backwards, so we will never be able to do that.  One such
129
        # known client is bzrlib.bundle.serializer.v4.get_bundle_reader. At the
130
        # time of this writing it's even the only known client -- vila20071203
3059.2.2 by Vincent Ladeuil
Read http responses on demand without buffering the whole body
131
        return StringIO(response_file.read())
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
132
2164.2.15 by Vincent Ladeuil
Http redirections are not followed by default. Do not use hints
133
    def _get(self, relpath, ranges, tail_amount=0):
1540.3.27 by Martin Pool
Integrate http range support for pycurl
134
        """Get a file, or part of a file.
135
136
        :param relpath: Path relative to transport base URL
2164.2.1 by v.ladeuil+lp at free
First rough http branch redirection implementation.
137
        :param ranges: None to get the whole file;
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
138
            or  a list of _CoalescedOffset to fetch parts of a file.
2164.2.26 by Vincent Ladeuil
Delete obsolete note in doc string.
139
        :param tail_amount: The amount to get from the end of the file.
1540.3.27 by Martin Pool
Integrate http range support for pycurl
140
141
        :returns: (http_code, result_file)
142
        """
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
143
        raise NotImplementedError(self._get)
1594.2.5 by Robert Collins
Readv patch from Johan Rydberg giving knits partial download support.
144
3133.1.2 by Vincent Ladeuil
Fix #177643 by making pycurl handle url-embedded credentials again.
145
    def _remote_path(self, relpath):
146
        """See ConnectedTransport._remote_path.
147
148
        user and passwords are not embedded in the path provided to the server.
149
        """
150
        relative = urlutils.unescape(relpath).encode('utf-8')
151
        path = self._combine_paths(self._path, relative)
152
        return self._unsplit_url(self._unqualified_scheme,
153
                                 None, None, self._host, self._port, path)
154
155
    def _create_auth(self):
156
        """Returns a dict returning the credentials provided at build time."""
157
        auth = dict(host=self._host, port=self._port,
158
                    user=self._user, password=self._password,
159
                    protocol=self._unqualified_scheme,
160
                    path=self._path)
161
        return auth
162
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
163
    def get_request(self):
2018.2.8 by Andrew Bennetts
Make HttpTransportBase.get_smart_client return self again.
164
        return SmartClientHTTPMediumRequest(self)
2018.2.6 by Andrew Bennetts
HTTP client starting to work (pycurl for the moment).
165
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
166
    def get_smart_medium(self):
167
        """See Transport.get_smart_medium.
168
169
        HttpTransportBase directly implements the minimal interface of
170
        SmartMediumClient, so this returns self.
171
        """
2018.2.8 by Andrew Bennetts
Make HttpTransportBase.get_smart_client return self again.
172
        return self
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
173
2520.2.2 by Vincent Ladeuil
Fix #115209 by issuing a single range request on 400: Bad Request
174
    def _degrade_range_hint(self, relpath, ranges, exc_info):
2000.3.9 by v.ladeuil+lp at free
The tests that would have help avoid bug #73948 and all that mess :)
175
        if self._range_hint == 'multi':
176
            self._range_hint = 'single'
2520.2.2 by Vincent Ladeuil
Fix #115209 by issuing a single range request on 400: Bad Request
177
            mutter('Retry "%s" with single range request' % relpath)
2000.3.9 by v.ladeuil+lp at free
The tests that would have help avoid bug #73948 and all that mess :)
178
        elif self._range_hint == 'single':
179
            self._range_hint = None
2520.2.2 by Vincent Ladeuil
Fix #115209 by issuing a single range request on 400: Bad Request
180
            mutter('Retry "%s" without ranges' % relpath)
2000.3.9 by v.ladeuil+lp at free
The tests that would have help avoid bug #73948 and all that mess :)
181
        else:
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
182
            # We tried all the tricks, but nothing worked. We re-raise the
183
            # original exception; the 'mutter' calls above will indicate that
184
            # further tries were unsuccessful
2172.3.1 by v.ladeuil+lp at free
Merge a recent bzr.dev (2172) and takes John's remarks into account.
185
            raise exc_info[0], exc_info[1], exc_info[2]
2000.3.9 by v.ladeuil+lp at free
The tests that would have help avoid bug #73948 and all that mess :)
186
2520.2.2 by Vincent Ladeuil
Fix #115209 by issuing a single range request on 400: Bad Request
187
    # _coalesce_offsets is a helper for readv, it try to combine ranges without
188
    # degrading readv performances. _bytes_to_read_before_seek is the value
189
    # used for the limit parameter and has been tuned for other transports. For
190
    # HTTP, the name is inappropriate but the parameter is still useful and
191
    # helps reduce the number of chunks in the response. The overhead for a
192
    # chunk (headers, length, footer around the data itself is variable but
193
    # around 50 bytes. We use 128 to reduce the range specifiers that appear in
194
    # the header, some servers (notably Apache) enforce a maximum length for a
195
    # header and issue a '400: Bad request' error when too much ranges are
196
    # specified.
197
    _bytes_to_read_before_seek = 128
198
    # No limit on the offset number that get combined into one, we are trying
3059.2.17 by Vincent Ladeuil
Limit GET requests by body size instead of number of ranges.
199
    # to avoid downloading the whole file.
3024.2.1 by Vincent Ladeuil
Fix 165061 by using the correct _max_readv_combine attribute.
200
    _max_readv_combine = 0
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
201
    # By default Apache has a limit of ~400 ranges before replying with a 400
202
    # Bad Request. So we go underneath that amount to be safe.
203
    _max_get_ranges = 200
3059.2.17 by Vincent Ladeuil
Limit GET requests by body size instead of number of ranges.
204
    # We impose no limit on the range size. But see _pycurl.py for a different
205
    # use.
206
    _get_max_size = 0
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
207
2745.5.1 by Robert Collins
* New parameter on ``bzrlib.transport.Transport.readv``
208
    def _readv(self, relpath, offsets):
1594.2.5 by Robert Collins
Readv patch from Johan Rydberg giving knits partial download support.
209
        """Get parts of the file at the given relative path.
210
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
211
        :param offsets: A list of (offset, size) tuples.
1540.3.27 by Martin Pool
Integrate http range support for pycurl
212
        :param return: A list or generator of (offset, data) tuples
1594.2.5 by Robert Collins
Readv patch from Johan Rydberg giving knits partial download support.
213
        """
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
214
3059.2.18 by Vincent Ladeuil
Take spiv review comments into account.
215
        # offsets may be a generator, we will iterate it several times, so
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
216
        # build a list
217
        offsets = list(offsets)
218
219
        try_again = True
3146.3.2 by Vincent Ladeuil
Fix #179368 by keeping the current range hint on ShortReadvErrors.
220
        retried_offset = None
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
221
        while try_again:
222
            try_again = False
223
224
            # Coalesce the offsets to minimize the GET requests issued
225
            sorted_offsets = sorted(offsets)
226
            coalesced = self._coalesce_offsets(
227
                sorted_offsets, limit=self._max_readv_combine,
3059.2.17 by Vincent Ladeuil
Limit GET requests by body size instead of number of ranges.
228
                fudge_factor=self._bytes_to_read_before_seek,
229
                max_size=self._get_max_size)
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
230
231
            # Turn it into a list, we will iterate it several times
232
            coalesced = list(coalesced)
233
            mutter('http readv of %s  offsets => %s collapsed %s',
234
                    relpath, len(offsets), len(coalesced))
235
236
            # Cache the data read, but only until it's been used
237
            data_map = {}
238
            # We will iterate on the data received from the GET requests and
3059.2.18 by Vincent Ladeuil
Take spiv review comments into account.
239
            # serve the corresponding offsets respecting the initial order. We
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
240
            # need an offset iterator for that.
241
            iter_offsets = iter(offsets)
242
            cur_offset_and_size = iter_offsets.next()
243
244
            try:
3059.2.10 by Vincent Ladeuil
Jam's review feedback.
245
                for cur_coal, rfile in self._coalesce_readv(relpath, coalesced):
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
246
                    # Split the received chunk
247
                    for offset, size in cur_coal.ranges:
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
248
                        start = cur_coal.start + offset
3059.2.10 by Vincent Ladeuil
Jam's review feedback.
249
                        rfile.seek(start, 0)
250
                        data = rfile.read(size)
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
251
                        data_len = len(data)
252
                        if data_len != size:
253
                            raise errors.ShortReadvError(relpath, start, size,
254
                                                         actual=data_len)
3059.2.5 by Vincent Ladeuil
DAMN^64, the http test server is 1.0 not 1.1 :( Better pipe cleaning and less readv caching (since that's the point of the whole fix).
255
                        if (start, size) == cur_offset_and_size:
256
                            # The offset requested are sorted as the coalesced
3059.2.11 by Vincent Ladeuil
Fix typos mentioned by spiv.
257
                            # ones, no need to cache. Win !
3059.2.5 by Vincent Ladeuil
DAMN^64, the http test server is 1.0 not 1.1 :( Better pipe cleaning and less readv caching (since that's the point of the whole fix).
258
                            yield cur_offset_and_size[0], data
259
                            cur_offset_and_size = iter_offsets.next()
260
                        else:
261
                            # Different sorting. We need to cache.
262
                            data_map[(start, size)] = data
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
263
264
                    # Yield everything we can
265
                    while cur_offset_and_size in data_map:
266
                        # Clean the cached data since we use it
267
                        # XXX: will break if offsets contains duplicates --
268
                        # vila20071129
269
                        this_data = data_map.pop(cur_offset_and_size)
270
                        yield cur_offset_and_size[0], this_data
271
                        cur_offset_and_size = iter_offsets.next()
272
3059.2.2 by Vincent Ladeuil
Read http responses on demand without buffering the whole body
273
            except (errors.ShortReadvError, errors.InvalidRange,
274
                    errors.InvalidHttpRange), e:
3146.3.2 by Vincent Ladeuil
Fix #179368 by keeping the current range hint on ShortReadvErrors.
275
                mutter('Exception %r: %s during http._readv',e, e)
276
                if (not isinstance(e, errors.ShortReadvError)
277
                    or retried_offset == cur_offset_and_size):
278
                    # We don't degrade the range hint for ShortReadvError since
279
                    # they do not indicate a problem with the server ability to
280
                    # handle ranges. Except when we fail to get back a required
281
                    # offset twice in a row. In that case, falling back to
282
                    # single range or whole file should help or end up in a
283
                    # fatal exception.
284
                    self._degrade_range_hint(relpath, coalesced, sys.exc_info())
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
285
                # Some offsets may have been already processed, so we retry
286
                # only the unsuccessful ones.
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
287
                offsets = [cur_offset_and_size] + [o for o in iter_offsets]
3146.3.2 by Vincent Ladeuil
Fix #179368 by keeping the current range hint on ShortReadvErrors.
288
                retried_offset = cur_offset_and_size
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
289
                try_again = True
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
290
291
    def _coalesce_readv(self, relpath, coalesced):
292
        """Issue several GET requests to satisfy the coalesced offsets"""
3059.2.17 by Vincent Ladeuil
Limit GET requests by body size instead of number of ranges.
293
294
        def get_and_yield(relpath, coalesced):
295
            if coalesced:
296
                # Note that the _get below may raise
3059.2.18 by Vincent Ladeuil
Take spiv review comments into account.
297
                # errors.InvalidHttpRange. It's the caller's responsibility to
3059.2.17 by Vincent Ladeuil
Limit GET requests by body size instead of number of ranges.
298
                # decide how to retry since it may provide different coalesced
299
                # offsets.
300
                code, rfile = self._get(relpath, coalesced)
301
                for coal in coalesced:
302
                    yield coal, rfile
303
304
        if self._range_hint is None:
305
            # Download whole file
306
            for c, rfile in get_and_yield(relpath, coalesced):
307
                yield c, rfile
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
308
        else:
3059.2.17 by Vincent Ladeuil
Limit GET requests by body size instead of number of ranges.
309
            total = len(coalesced)
310
            if self._range_hint == 'multi':
311
                max_ranges = self._max_get_ranges
3059.2.18 by Vincent Ladeuil
Take spiv review comments into account.
312
            elif self._range_hint == 'single':
3059.2.17 by Vincent Ladeuil
Limit GET requests by body size instead of number of ranges.
313
                max_ranges = total
3059.2.18 by Vincent Ladeuil
Take spiv review comments into account.
314
            else:
315
                raise AssertionError("Unknown _range_hint %r"
316
                                     % (self._range_hint,))
3059.2.17 by Vincent Ladeuil
Limit GET requests by body size instead of number of ranges.
317
            # TODO: Some web servers may ignore the range requests and return
318
            # the whole file, we may want to detect that and avoid further
319
            # requests.
320
            # Hint: test_readv_multiple_get_requests will fail once we do that
321
            cumul = 0
322
            ranges = []
323
            for coal in coalesced:
324
                if ((self._get_max_size > 0
325
                     and cumul + coal.length > self._get_max_size)
326
                    or len(ranges) >= max_ranges):
327
                    # Get that much and yield
328
                    for c, rfile in get_and_yield(relpath, ranges):
329
                        yield c, rfile
330
                    # Restart with the current offset
331
                    ranges = [coal]
332
                    cumul = coal.length
333
                else:
334
                    ranges.append(coal)
335
                    cumul += coal.length
336
            # Get the rest and yield
337
            for c, rfile in get_and_yield(relpath, ranges):
338
                yield c, rfile
1786.1.5 by John Arbash Meinel
Move the common Multipart stuff into plain http, and wrap pycurl response so that it matches the urllib response object.
339
2671.3.1 by Robert Collins
* New method ``bzrlib.transport.Transport.get_recommended_page_size``.
340
    def recommended_page_size(self):
341
        """See Transport.recommended_page_size().
342
343
        For HTTP we suggest a large page size to reduce the overhead
344
        introduced by latency.
345
        """
346
        return 64 * 1024
347
2018.2.10 by Andrew Bennetts
Tidy up TODOs, further testing and fixes for SmartServerRequestProtocolOne, and remove a read_bytes(1) call.
348
    def _post(self, body_bytes):
349
        """POST body_bytes to .bzr/smart on this transport.
350
        
351
        :returns: (response code, response body file-like object).
352
        """
353
        # TODO: Requiring all the body_bytes to be available at the beginning of
354
        # the POST may require large client buffers.  It would be nice to have
355
        # an interface that allows streaming via POST when possible (and
356
        # degrades to a local buffer when not).
357
        raise NotImplementedError(self._post)
358
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
359
    def put_file(self, relpath, f, mode=None):
360
        """Copy the file-like object into the location.
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
361
362
        :param relpath: Location to put the contents, relative to base.
1955.3.6 by John Arbash Meinel
Lots of deprecation warnings, but no errors
363
        :param f:       File-like object.
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
364
        """
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
365
        raise errors.TransportNotPossible('http PUT not supported')
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
366
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
367
    def mkdir(self, relpath, mode=None):
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
368
        """Create a directory at the given path."""
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
369
        raise errors.TransportNotPossible('http does not support mkdir()')
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
370
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
371
    def rmdir(self, relpath):
372
        """See Transport.rmdir."""
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
373
        raise errors.TransportNotPossible('http does not support rmdir()')
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
374
1955.3.15 by John Arbash Meinel
Deprecate 'Transport.append' in favor of Transport.append_file or Transport.append_bytes
375
    def append_file(self, relpath, f, mode=None):
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
376
        """Append the text in the file-like object into the final
377
        location.
378
        """
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
379
        raise errors.TransportNotPossible('http does not support append()')
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
380
381
    def copy(self, rel_from, rel_to):
382
        """Copy the item at rel_from to the location at rel_to"""
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
383
        raise errors.TransportNotPossible('http does not support copy()')
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
384
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
385
    def copy_to(self, relpaths, other, mode=None, pb=None):
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
386
        """Copy a set of entries from self into another Transport.
387
388
        :param relpaths: A list/generator of entries to be copied.
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
389
390
        TODO: if other is LocalTransport, is it possible to
391
              do better than put(get())?
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
392
        """
907.1.29 by John Arbash Meinel
Fixing small bug in HttpTransport.copy_to
393
        # At this point HttpTransport might be able to check and see if
394
        # the remote location is the same, and rather than download, and
395
        # then upload, it could just issue a remote copy_this command.
1540.3.6 by Martin Pool
[merge] update from bzr.dev
396
        if isinstance(other, HttpTransportBase):
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
397
            raise errors.TransportNotPossible(
398
                'http cannot be the target of copy_to()')
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
399
        else:
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
400
            return super(HttpTransportBase, self).\
401
                    copy_to(relpaths, other, mode=mode, pb=pb)
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
402
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
403
    def move(self, rel_from, rel_to):
404
        """Move the item at rel_from to the location at rel_to"""
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
405
        raise errors.TransportNotPossible('http does not support move()')
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
406
407
    def delete(self, relpath):
408
        """Delete the item at relpath"""
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
409
        raise errors.TransportNotPossible('http does not support delete()')
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
410
2634.1.1 by Robert Collins
(robertc) Reinstate the accidentally backed out external_url patch.
411
    def external_url(self):
412
        """See bzrlib.transport.Transport.external_url."""
413
        # HTTP URL's are externally usable.
414
        return self.base
415
1530.1.3 by Robert Collins
transport implementations now tested consistently.
416
    def is_readonly(self):
417
        """See Transport.is_readonly."""
418
        return True
419
1400.1.1 by Robert Collins
implement a basic test for the ui branch command from http servers
420
    def listable(self):
421
        """See Transport.listable."""
422
        return False
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
423
424
    def stat(self, relpath):
425
        """Return the stat information for a file.
426
        """
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
427
        raise errors.TransportNotPossible('http does not support stat()')
907.1.21 by John Arbash Meinel
Adding http transport as a valid transport protocol.
428
907.1.24 by John Arbash Meinel
Remote functionality work.
429
    def lock_read(self, relpath):
430
        """Lock the given file for shared (read) access.
431
        :return: A lock object, which should be passed to Transport.unlock()
432
        """
433
        # The old RemoteBranch ignore lock for reading, so we will
434
        # continue that tradition and return a bogus lock object.
435
        class BogusLock(object):
436
            def __init__(self, path):
437
                self.path = path
438
            def unlock(self):
439
                pass
440
        return BogusLock(relpath)
441
442
    def lock_write(self, relpath):
443
        """Lock the given file for exclusive (write) access.
444
        WARNING: many transports do not support this, so trying avoid using it
445
446
        :return: A lock object, which should be passed to Transport.unlock()
447
        """
2004.1.25 by v.ladeuil+lp at free
Shuffle http related test code. Hopefully it ends up at the right place :)
448
        raise errors.TransportNotPossible('http does not support lock_write()')
1530.1.1 by Robert Collins
Minimal infrastructure to test TransportTestProviderAdapter.
449
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
450
    def clone(self, offset=None):
451
        """Return a new HttpTransportBase with root at self.base + offset
2025.2.1 by v.ladeuil+lp at free
Fix bug #61606 by providing cloning hint do daughter classes.
452
2004.1.6 by vila
Connection sharing between cloned transports.
453
        We leave the daughter classes take advantage of the hint
454
        that it's a cloning not a raw creation.
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
455
        """
456
        if offset is None:
2004.1.6 by vila
Connection sharing between cloned transports.
457
            return self.__class__(self.base, self)
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
458
        else:
2004.1.6 by vila
Connection sharing between cloned transports.
459
            return self.__class__(self.abspath(offset), self)
1530.1.1 by Robert Collins
Minimal infrastructure to test TransportTestProviderAdapter.
460
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
461
    def _attempted_range_header(self, offsets, tail_amount):
3059.2.17 by Vincent Ladeuil
Limit GET requests by body size instead of number of ranges.
462
        """Prepare a HTTP Range header at a level the server should accept.
463
464
        :return: the range header representing offsets/tail_amount or None if
465
            no header can be built.
466
        """
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
467
468
        if self._range_hint == 'multi':
3024.2.3 by Vincent Ladeuil
Rewrite http_readv to allow several GET requests. Smoke tested against branch reported in the bug.
469
            # Generate the header describing all offsets
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
470
            return self._range_header(offsets, tail_amount)
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
471
        elif self._range_hint == 'single':
472
            # Combine all the requested ranges into a single
473
            # encompassing one
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
474
            if len(offsets) > 0:
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
475
                if tail_amount not in (0, None):
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
476
                    # Nothing we can do here to combine ranges with tail_amount
477
                    # in a single range, just returns None. The whole file
478
                    # should be downloaded.
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
479
                    return None
480
                else:
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
481
                    start = offsets[0].start
482
                    last = offsets[-1]
483
                    end = last.start + last.length - 1
484
                    whole = self._coalesce_offsets([(start, end - start + 1)],
485
                                                   limit=0, fudge_factor=0)
486
                    return self._range_header(list(whole), 0)
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
487
            else:
488
                # Only tail_amount, requested, leave range_header
489
                # do its work
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
490
                return self._range_header(offsets, tail_amount)
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
491
        else:
492
            return None
493
1786.1.27 by John Arbash Meinel
Fix up the http transports so that tests pass with the new configuration.
494
    @staticmethod
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
495
    def _range_header(ranges, tail_amount):
1750.1.2 by Michael Ellerman
Add support for HTTP multipart ranges and hook it into http+urllib.
496
        """Turn a list of bytes ranges into a HTTP Range header value.
497
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
498
        :param ranges: A list of _CoalescedOffset
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
499
        :param tail_amount: The amount to get from the end of the file.
1750.1.2 by Michael Ellerman
Add support for HTTP multipart ranges and hook it into http+urllib.
500
501
        :return: HTTP range header string.
2004.1.30 by v.ladeuil+lp at free
Fix #62276 and #62029 by providing a more robust http range handling.
502
503
        At least a non-empty ranges *or* a tail_amount must be
504
        provided.
1750.1.2 by Michael Ellerman
Add support for HTTP multipart ranges and hook it into http+urllib.
505
        """
506
        strings = []
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
507
        for offset in ranges:
508
            strings.append('%d-%d' % (offset.start,
509
                                      offset.start + offset.length - 1))
1750.1.2 by Michael Ellerman
Add support for HTTP multipart ranges and hook it into http+urllib.
510
1786.1.8 by John Arbash Meinel
[merge] Johan Rydberg test updates
511
        if tail_amount:
512
            strings.append('-%d' % tail_amount)
513
1786.1.36 by John Arbash Meinel
pycurl expects us to just set the range of bytes, not including bytes=
514
        return ','.join(strings)
1750.1.2 by Michael Ellerman
Add support for HTTP multipart ranges and hook it into http+urllib.
515
2018.2.8 by Andrew Bennetts
Make HttpTransportBase.get_smart_client return self again.
516
    def send_http_smart_request(self, bytes):
3241.1.4 by Andrew Bennetts
Use get_smart_medium as suggested by Robert, and deal with the fallout.
517
        try:
518
            code, body_filelike = self._post(bytes)
519
            if code != 200:
520
                raise InvalidHttpResponse(
521
                    self._remote_path('.bzr/smart'),
522
                    'Expected 200 response code, got %r' % (code,))
523
        except errors.InvalidHttpResponse, e:
524
            raise errors.SmartProtocolError(str(e))
2018.2.8 by Andrew Bennetts
Make HttpTransportBase.get_smart_client return self again.
525
        return body_filelike
526
527
2018.5.2 by Andrew Bennetts
Start splitting bzrlib/transport/smart.py into a package.
528
class SmartClientHTTPMediumRequest(medium.SmartClientMediumRequest):
2018.2.8 by Andrew Bennetts
Make HttpTransportBase.get_smart_client return self again.
529
    """A SmartClientMediumRequest that works with an HTTP medium."""
530
2018.5.2 by Andrew Bennetts
Start splitting bzrlib/transport/smart.py into a package.
531
    def __init__(self, client_medium):
532
        medium.SmartClientMediumRequest.__init__(self, client_medium)
2018.2.8 by Andrew Bennetts
Make HttpTransportBase.get_smart_client return self again.
533
        self._buffer = ''
534
535
    def _accept_bytes(self, bytes):
536
        self._buffer += bytes
537
538
    def _finished_writing(self):
539
        data = self._medium.send_http_smart_request(self._buffer)
540
        self._response_body = data
541
542
    def _read_bytes(self, count):
543
        return self._response_body.read(count)
2004.1.28 by v.ladeuil+lp at free
Merge bzr.dev. Including http modifications by "smart" related code
544
2018.2.8 by Andrew Bennetts
Make HttpTransportBase.get_smart_client return self again.
545
    def _finished_reading(self):
546
        """See SmartClientMediumRequest._finished_reading."""
547
        pass