Merge lp:~mbp/bzr/198646-http-boundary-2.3 into lp:bzr/2.3

Proposed by Martin Pool
Status: Superseded
Proposed branch: lp:~mbp/bzr/198646-http-boundary-2.3
Merge into: lp:bzr/2.3
Diff against target: 137 lines (+88/-2)
4 files modified
bzrlib/errors.py (+13/-0)
bzrlib/tests/test_http.py (+66/-0)
bzrlib/transport/http/__init__.py (+1/-1)
bzrlib/transport/http/response.py (+8/-1)
To merge this branch: bzr merge lp:~mbp/bzr/198646-http-boundary-2.3
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+69440@code.launchpad.net

Commit message

cope with proxies that drop the connection during a multipart response (bug 198646)

Description of the change

bug 198646 is that some buggy squids can close a connection in the middle of a mime multipart request, before the boundary line; users sometimes hit this and can't easily change their corporate proxy. hat tip to Henrik for the clear description.

This builds on a patch attached to that bug to handle this more cleanly, and it adds a test that does trigger the behaviour.

The test is a bit copy-pasty, but it does cover the case and it's not awful.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

It might be nice to note that retries will happen in the log, in case folk are confused/concerned by bzr being unexpectedly slow.

review: Approve
Revision history for this message
Martin Pool (mbp) wrote :

> It might be nice to note that retries will happen in the log, in case folk are
> confused/concerned by bzr being unexpectedly slow.

+1, it's not obvious here but _degrade_range_hint should be doing that.

Revision history for this message
Martin Pool (mbp) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/errors.py'
2--- bzrlib/errors.py 2011-01-20 21:15:10 +0000
3+++ bzrlib/errors.py 2011-07-27 12:03:30 +0000
4@@ -1722,6 +1722,19 @@
5 InvalidHttpResponse.__init__(self, path, msg)
6
7
8+class HttpBoundaryMissing(InvalidHttpResponse):
9+ """A multipart response ends with no boundary marker.
10+
11+ This is a special case caused by buggy proxies, described in
12+ <https://bugs.launchpad.net/bzr/+bug/198646>.
13+ """
14+
15+ _fmt = "HTTP MIME Boundary missing for %(path)s: %(msg)s"
16+
17+ def __init__(self, path, msg):
18+ InvalidHttpResponse.__init__(self, path, msg)
19+
20+
21 class InvalidHttpContentType(InvalidHttpResponse):
22
23 _fmt = 'Invalid http Content-type "%(ctype)s" for %(path)s: %(msg)s'
24
25=== modified file 'bzrlib/tests/test_http.py'
26--- bzrlib/tests/test_http.py 2011-01-12 22:21:05 +0000
27+++ bzrlib/tests/test_http.py 2011-07-27 12:03:30 +0000
28@@ -999,6 +999,72 @@
29 self.assertEqual('single', t._range_hint)
30
31
32+class TruncatedBeforeBoundaryRequestHandler(
33+ http_server.TestingHTTPRequestHandler):
34+ """Truncation before a boundary, like in bug 198646"""
35+
36+ _truncated_ranges = 1
37+
38+ def get_multiple_ranges(self, file, file_size, ranges):
39+ self.send_response(206)
40+ self.send_header('Accept-Ranges', 'bytes')
41+ boundary = 'tagada'
42+ self.send_header('Content-Type',
43+ 'multipart/byteranges; boundary=%s' % boundary)
44+ boundary_line = '--%s\r\n' % boundary
45+ # Calculate the Content-Length
46+ content_length = 0
47+ for (start, end) in ranges:
48+ content_length += len(boundary_line)
49+ content_length += self._header_line_length(
50+ 'Content-type', 'application/octet-stream')
51+ content_length += self._header_line_length(
52+ 'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
53+ content_length += len('\r\n') # end headers
54+ content_length += end - start # + 1
55+ content_length += len(boundary_line)
56+ self.send_header('Content-length', content_length)
57+ self.end_headers()
58+
59+ # Send the multipart body
60+ cur = 0
61+ for (start, end) in ranges:
62+ if cur + self._truncated_ranges >= len(ranges):
63+ # Abruptly ends the response and close the connection
64+ self.close_connection = 1
65+ return
66+ self.wfile.write(boundary_line)
67+ self.send_header('Content-type', 'application/octet-stream')
68+ self.send_header('Content-Range', 'bytes %d-%d/%d'
69+ % (start, end, file_size))
70+ self.end_headers()
71+ self.send_range_content(file, start, end - start + 1)
72+ cur += 1
73+ # Final boundary
74+ self.wfile.write(boundary_line)
75+
76+
77+class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
78+ """Tests the case of bug 198646, disconnecting before a boundary."""
79+
80+ _req_handler_class = TruncatedBeforeBoundaryRequestHandler
81+
82+ def setUp(self):
83+ super(TestTruncatedBeforeBoundary, self).setUp()
84+ self.build_tree_contents([('a', '0123456789')],)
85+
86+ def test_readv_with_short_reads(self):
87+ server = self.get_readonly_server()
88+ t = self.get_readonly_transport()
89+ # Force separate ranges for each offset
90+ t._bytes_to_read_before_seek = 0
91+ ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
92+ self.assertEqual((0, '0'), ireadv.next())
93+ self.assertEqual((2, '2'), ireadv.next())
94+ self.assertEqual((4, '45'), ireadv.next())
95+ self.assertEqual((9, '9'), ireadv.next())
96+
97+
98 class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
99 """Errors out when range specifiers exceed the limit"""
100
101
102=== modified file 'bzrlib/transport/http/__init__.py'
103--- bzrlib/transport/http/__init__.py 2011-01-26 19:34:58 +0000
104+++ bzrlib/transport/http/__init__.py 2011-07-27 12:03:30 +0000
105@@ -271,7 +271,7 @@
106 cur_offset_and_size = iter_offsets.next()
107
108 except (errors.ShortReadvError, errors.InvalidRange,
109- errors.InvalidHttpRange), e:
110+ errors.InvalidHttpRange, errors.HttpBoundaryMissing), e:
111 mutter('Exception %r: %s during http._readv',e, e)
112 if (not isinstance(e, errors.ShortReadvError)
113 or retried_offset == cur_offset_and_size):
114
115=== modified file 'bzrlib/transport/http/response.py'
116--- bzrlib/transport/http/response.py 2009-03-23 14:59:43 +0000
117+++ bzrlib/transport/http/response.py 2011-07-27 12:03:30 +0000
118@@ -1,4 +1,4 @@
119-# Copyright (C) 2006, 2007 Canonical Ltd
120+# Copyright (C) 2006-2011 Canonical Ltd
121 #
122 # This program is free software; you can redistribute it and/or modify
123 # it under the terms of the GNU General Public License as published by
124@@ -109,6 +109,13 @@
125 # To be on the safe side we allow it before any boundary line
126 boundary_line = self._file.readline()
127
128+ if boundary_line == '':
129+ # A timeout in the proxy server caused the response to end early.
130+ # See launchpad bug 198646.
131+ raise errors.HttpBoundaryMissing(
132+ self._path,
133+ self._boundary)
134+
135 if boundary_line != '--' + self._boundary + '\r\n':
136 # rfc822.unquote() incorrectly unquotes strings enclosed in <>
137 # IIS 6 and 7 incorrectly wrap boundary strings in <>

Subscribers

People subscribed via source and target branches