~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/__init__.py

  • Committer: Vincent Ladeuil
  • Date: 2007-12-08 23:15:18 UTC
  • mto: (2929.3.16 https) (3097.2.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 3099.
  • Revision ID: v.ladeuil+lp@free.fr-20071208231518-sj2ui57xyd4mkjra
Limit GET requests by body size instead of number of ranges.

* bzrlib/transport/http/response.py:
Fix some error messages.
(RangeFile.read_range_definition): Keep the range headers in the
_headers attribute for easier debugging (especially for remote
debugging).

* bzrlib/transport/http/_pycurl.py:
(PyCurlTransport): Replace _max_readv_combine by _get_max_size
which is more approriate to the problem.

* bzrlib/transport/http/__init__.py:
(HttpTransportBase): Add a _get_max_size class attribute
corresponding to the max_size _coalesced_offets max_size
parameter.
(HttpTransportBase._coalesce_readv): Limit the size of the get
requests if _get_max_size is greater than 0 while still respecting
the maximum number of ranges in a request.

* bzrlib/tests/test_http.py:
(TestRangeRequestServer.test_readv_get_max_size): Test the
_get_max_size parameter.

* bzrlib/transport/__init__.py:
(Transport._coalesce_offsets): Add a max_size parameter limiting
the size of the coalesced offsets.

* bzrlib/tests/test_transport.py:
(TestCoalesceOffsets.check): Add the max_size parameter.
(TestCoalesceOffsets.test_coalesce_max_size,
TestCoalesceOffsets.test_coalesce_no_max_size): Test the max_size
parameter.

Show diffs side-by-side

added added

removed removed

Lines of Context:
188
188
    # specified.
189
189
    _bytes_to_read_before_seek = 128
190
190
    # No limit on the offset number that get combined into one, we are trying
191
 
    # to avoid downloading the whole file. But see _pycurl.py for a different
192
 
    # use.
 
191
    # to avoid downloading the whole file.
193
192
    _max_readv_combine = 0
194
193
    # By default Apache has a limit of ~400 ranges before replying with a 400
195
194
    # Bad Request. So we go underneath that amount to be safe.
196
195
    _max_get_ranges = 200
 
196
    # We impose no limit on the range size. But see _pycurl.py for a different
 
197
    # use.
 
198
    _get_max_size = 0
197
199
 
198
200
    def _readv(self, relpath, offsets):
199
201
        """Get parts of the file at the given relative path.
214
216
            sorted_offsets = sorted(offsets)
215
217
            coalesced = self._coalesce_offsets(
216
218
                sorted_offsets, limit=self._max_readv_combine,
217
 
                fudge_factor=self._bytes_to_read_before_seek)
 
219
                fudge_factor=self._bytes_to_read_before_seek,
 
220
                max_size=self._get_max_size)
218
221
 
219
222
            # Turn it into a list, we will iterate it several times
220
223
            coalesced = list(coalesced)
268
271
 
269
272
    def _coalesce_readv(self, relpath, coalesced):
270
273
        """Issue several GET requests to satisfy the coalesced offsets"""
271
 
        total = len(coalesced)
272
 
        if self._range_hint == 'multi':
273
 
             max_ranges = self._max_get_ranges
274
 
        elif self._range_hint == 'single':
275
 
             max_ranges = total
 
274
 
 
275
        def get_and_yield(relpath, coalesced):
 
276
            if coalesced:
 
277
                # Note that the _get below may raise
 
278
                # errors.InvalidHttpRange. It's the caller's responsability to
 
279
                # decide how to retry since it may provide different coalesced
 
280
                # offsets.
 
281
                code, rfile = self._get(relpath, coalesced)
 
282
                for coal in coalesced:
 
283
                    yield coal, rfile
 
284
 
 
285
        if self._range_hint is None:
 
286
            # Download whole file
 
287
            for c, rfile in get_and_yield(relpath, coalesced):
 
288
                yield c, rfile
276
289
        else:
277
 
            # The whole file will be downloaded anyway
278
 
            max_ranges = total
279
 
        # TODO: Some web servers may ignore the range requests and return the
280
 
        # whole file, we may want to detect that and avoid further requests.
281
 
        # Hint: test_readv_multiple_get_requests will fail once we do that
282
 
        for group in xrange(0, len(coalesced), max_ranges):
283
 
            ranges = coalesced[group:group+max_ranges]
284
 
            # Note that the following may raise errors.InvalidHttpRange. It's
285
 
            # the caller's responsability to decide how to retry since it may
286
 
            # provide different coalesced offsets.
287
 
            code, rfile = self._get(relpath, ranges)
288
 
            for range in ranges:
289
 
                yield range, rfile
 
290
            total = len(coalesced)
 
291
            if self._range_hint == 'multi':
 
292
                max_ranges = self._max_get_ranges
 
293
            else: # self._range_hint == 'single'
 
294
                max_ranges = total
 
295
            # TODO: Some web servers may ignore the range requests and return
 
296
            # the whole file, we may want to detect that and avoid further
 
297
            # requests.
 
298
            # Hint: test_readv_multiple_get_requests will fail once we do that
 
299
            cumul = 0
 
300
            ranges = []
 
301
            for coal in coalesced:
 
302
                if ((self._get_max_size > 0
 
303
                     and cumul + coal.length > self._get_max_size)
 
304
                    or len(ranges) >= max_ranges):
 
305
                    # Get that much and yield
 
306
                    for c, rfile in get_and_yield(relpath, ranges):
 
307
                        yield c, rfile
 
308
                    # Restart with the current offset
 
309
                    ranges = [coal]
 
310
                    cumul = coal.length
 
311
                else:
 
312
                    ranges.append(coal)
 
313
                    cumul += coal.length
 
314
            # Get the rest and yield
 
315
            for c, rfile in get_and_yield(relpath, ranges):
 
316
                yield c, rfile
290
317
 
291
318
    def recommended_page_size(self):
292
319
        """See Transport.recommended_page_size().
410
437
            return self.__class__(self.abspath(offset), self)
411
438
 
412
439
    def _attempted_range_header(self, offsets, tail_amount):
413
 
        """Prepare a HTTP Range header at a level the server should accept"""
 
440
        """Prepare a HTTP Range header at a level the server should accept.
 
441
 
 
442
        :return: the range header representing offsets/tail_amount or None if
 
443
            no header can be built.
 
444
        """
414
445
 
415
446
        if self._range_hint == 'multi':
416
447
            # Generate the header describing all offsets