~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Vincent Ladeuil
  • Date: 2008-01-02 14:13:55 UTC
  • mto: (3159.1.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 3161.
  • Revision ID: v.ladeuil+lp@free.fr-20080102141355-k20yfjo6i1dasuny
Fix #179368 by keeping the current range hint on ShortReadvErrors.

* response.py:
(RangeFile._checked_read): Avoid huge buffering when huge seeks
are required.

* _urllib2_wrappers.py:
(Response.finish): Check for end-of-file or we'll loop if the
server lied about Content-Length.

* __init__.py:
(HttpTransportBase._readv): When a ShortReadvError occurs, try
again, staying in multiple range mode and degrades only if the
error occurs again for the same offset.

* test_http_response.py:
(TestRangeFileMultipleRanges): Add a test to exercise the buffer
overflow protection code.

* test_http.py:
(TestMultipleRangeWithoutContentLengthServer): Emulate lighttpd
behavior regarding bug #179368.

Show diffs side-by-side

added added

removed removed

Lines of Context:
905
905
 
906
906
    _req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
907
907
 
 
908
 
 
909
class TruncatedMultipleRangeRequestHandler(
 
910
    http_server.TestingHTTPRequestHandler):
 
911
    """Reply to multiple range requests truncating the last ones.
 
912
 
 
913
    This server generates responses whose Content-Length describes all the
 
914
    ranges, but fail to include the last ones leading to client short reads.
 
915
    This has been observed randomly with lighttpd (bug #179368).
 
916
    """
 
917
 
 
918
    _truncated_ranges = 2
 
919
 
 
920
    def get_multiple_ranges(self, file, file_size, ranges):
 
921
        self.send_response(206)
 
922
        self.send_header('Accept-Ranges', 'bytes')
 
923
        boundary = 'tagada'
 
924
        self.send_header('Content-Type',
 
925
                         'multipart/byteranges; boundary=%s' % boundary)
 
926
        boundary_line = '--%s\r\n' % boundary
 
927
        # Calculate the Content-Length
 
928
        content_length = 0
 
929
        for (start, end) in ranges:
 
930
            content_length += len(boundary_line)
 
931
            content_length += self._header_line_length(
 
932
                'Content-type', 'application/octet-stream')
 
933
            content_length += self._header_line_length(
 
934
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
 
935
            content_length += len('\r\n') # end headers
 
936
            content_length += end - start # + 1
 
937
        content_length += len(boundary_line)
 
938
        self.send_header('Content-length', content_length)
 
939
        self.end_headers()
 
940
 
 
941
        # Send the multipart body
 
942
        cur = 0
 
943
        for (start, end) in ranges:
 
944
            self.wfile.write(boundary_line)
 
945
            self.send_header('Content-type', 'application/octet-stream')
 
946
            self.send_header('Content-Range', 'bytes %d-%d/%d'
 
947
                             % (start, end, file_size))
 
948
            self.end_headers()
 
949
            if cur + self._truncated_ranges >= len(ranges):
 
950
                # Abruptly ends the response and close the connection
 
951
                self.close_connection = 1
 
952
                return
 
953
            self.send_range_content(file, start, end - start + 1)
 
954
            cur += 1
 
955
        # No final boundary
 
956
        self.wfile.write(boundary_line)
 
957
 
 
958
 
 
959
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
 
960
 
 
961
    _req_handler_class = TruncatedMultipleRangeRequestHandler
 
962
 
 
963
    def setUp(self):
 
964
        super(TestTruncatedMultipleRangeServer, self).setUp()
 
965
        self.build_tree_contents([('a', '0123456789')],)
 
966
 
 
967
    def test_readv_with_short_reads(self):
 
968
        server = self.get_readonly_server()
 
969
        t = self._transport(server.get_url())
 
970
        # Force separate ranges for each offset
 
971
        t._bytes_to_read_before_seek = 0
 
972
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
 
973
        self.assertEqual((0, '0'), ireadv.next())
 
974
        self.assertEqual((2, '2'), ireadv.next())
 
975
        if not self._testing_pycurl():
 
976
            # Only one request have been issued so far (except for pycurl that
 
977
            # try to read the whole response at once)
 
978
            self.assertEqual(1, server.GET_request_nb)
 
979
        self.assertEqual((4, '45'), ireadv.next())
 
980
        self.assertEqual((9, '9'), ireadv.next())
 
981
        # Both implementations issue 3 requests but:
 
982
        # - urllib does two multiple (4 ranges, then 2 ranges) then a single
 
983
        #   range,
 
984
        # - pycurl does two multiple (4 ranges, 4 ranges) then a single range
 
985
        self.assertEqual(3, server.GET_request_nb)
 
986
        # Finally the client have tried a single range request and stays in
 
987
        # that mode
 
988
        self.assertEqual('single', t._range_hint)
 
989
 
908
990
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
909
991
    """Errors out when range specifiers exceed the limit"""
910
992