Show More
@@ -112,6 +112,10 b' class HTTPResponse(object):' | |||||
112 |
|
112 | |||
113 | def complete(self): |
|
113 | def complete(self): | |
114 | """Returns true if this response is completely loaded. |
|
114 | """Returns true if this response is completely loaded. | |
|
115 | ||||
|
116 | Note that if this is a connection where complete means the | |||
|
117 | socket is closed, this will nearly always return False, even | |||
|
118 | in cases where all the data has actually been loaded. | |||
115 | """ |
|
119 | """ | |
116 | if self._chunked: |
|
120 | if self._chunked: | |
117 | return self._chunked_done |
|
121 | return self._chunked_done | |
@@ -174,10 +178,7 b' class HTTPResponse(object):' | |||||
174 | return True |
|
178 | return True | |
175 | logger.debug('response read %d data during _select', len(data)) |
|
179 | logger.debug('response read %d data during _select', len(data)) | |
176 | if not data: |
|
180 | if not data: | |
177 | if not self.headers: |
|
181 | if self.headers and self._content_len == _LEN_CLOSE_IS_END: | |
178 | self._load_response(self._end_headers) |
|
|||
179 | self._content_len = 0 |
|
|||
180 | elif self._content_len == _LEN_CLOSE_IS_END: |
|
|||
181 | self._content_len = len(self._body) |
|
182 | self._content_len = len(self._body) | |
182 | return False |
|
183 | return False | |
183 | else: |
|
184 | else: | |
@@ -561,17 +562,46 b' class HTTPConnection(object):' | |||||
561 | continue |
|
562 | continue | |
562 | if not data: |
|
563 | if not data: | |
563 | logger.info('socket appears closed in read') |
|
564 | logger.info('socket appears closed in read') | |
564 |
|
|
565 | self.sock = None | |
565 |
|
|
566 | self._current_response = None | |
|
567 | # This if/elif ladder is a bit subtle, | |||
|
568 | # comments in each branch should help. | |||
|
569 | if response is not None and ( | |||
|
570 | response.complete() or | |||
|
571 | response._content_len == _LEN_CLOSE_IS_END): | |||
|
572 | # Server responded completely and then | |||
|
573 | # closed the socket. We should just shut | |||
|
574 | # things down and let the caller get their | |||
|
575 | # response. | |||
|
576 | logger.info('Got an early response, ' | |||
|
577 | 'aborting remaining request.') | |||
|
578 | break | |||
|
579 | elif was_first and response is None: | |||
|
580 | # Most likely a keepalive that got killed | |||
|
581 | # on the server's end. Commonly happens | |||
|
582 | # after getting a really large response | |||
|
583 | # from the server. | |||
|
584 | logger.info( | |||
|
585 | 'Connection appeared closed in read on first' | |||
|
586 | ' request loop iteration, will retry.') | |||
|
587 | reconnect('read') | |||
|
588 | continue | |||
|
589 | else: | |||
|
590 | # We didn't just send the first data hunk, | |||
|
591 | # and either have a partial response or no | |||
|
592 | # response at all. There's really nothing | |||
|
593 | # meaningful we can do here. | |||
|
594 | raise HTTPStateError( | |||
|
595 | 'Connection appears closed after ' | |||
|
596 | 'some request data was written, but the ' | |||
|
597 | 'response was missing or incomplete!') | |||
|
598 | logger.debug('read %d bytes in request()', len(data)) | |||
566 | if response is None: |
|
599 | if response is None: | |
567 | response = self.response_class(r[0], self.timeout) |
|
600 | response = self.response_class(r[0], self.timeout) | |
568 | response._load_response(data) |
|
601 | response._load_response(data) | |
569 | if (response._content_len == _LEN_CLOSE_IS_END |
|
602 | # Jump to the next select() call so we load more | |
570 | and len(data) == 0): |
|
603 | # data if the server is still sending us content. | |
571 | response._content_len = len(response._body) |
|
604 | continue | |
572 | if response.complete(): |
|
|||
573 | w = [] |
|
|||
574 | response.will_close = True |
|
|||
575 | except socket.error, e: |
|
605 | except socket.error, e: | |
576 | if e[0] != errno.EPIPE and not was_first: |
|
606 | if e[0] != errno.EPIPE and not was_first: | |
577 | raise |
|
607 | raise | |
@@ -662,4 +692,7 b' class BadRequestData(httplib.HTTPExcepti' | |||||
662 |
|
692 | |||
663 | class HTTPProxyConnectFailedException(httplib.HTTPException): |
|
693 | class HTTPProxyConnectFailedException(httplib.HTTPException): | |
664 | """Connecting to the HTTP proxy failed.""" |
|
694 | """Connecting to the HTTP proxy failed.""" | |
|
695 | ||||
|
696 | class HTTPStateError(httplib.HTTPException): | |||
|
697 | """Invalid internal state encountered.""" | |||
665 | # no-check-code |
|
698 | # no-check-code |
@@ -26,6 +26,7 b'' | |||||
26 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
26 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
27 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
27 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
|
29 | import socket | |||
29 | import unittest |
|
30 | import unittest | |
30 |
|
31 | |||
31 | import http |
|
32 | import http | |
@@ -39,7 +40,7 b' class SimpleHttpTest(util.HttpTestBase, ' | |||||
39 | def _run_simple_test(self, host, server_data, expected_req, expected_data): |
|
40 | def _run_simple_test(self, host, server_data, expected_req, expected_data): | |
40 | con = http.HTTPConnection(host) |
|
41 | con = http.HTTPConnection(host) | |
41 | con._connect() |
|
42 | con._connect() | |
42 |
con.sock.data |
|
43 | con.sock.data = server_data | |
43 | con.request('GET', '/') |
|
44 | con.request('GET', '/') | |
44 |
|
45 | |||
45 | self.assertStringEqual(expected_req, con.sock.sent) |
|
46 | self.assertStringEqual(expected_req, con.sock.sent) | |
@@ -353,4 +354,33 b' dotencode' | |||||
353 |
|
354 | |||
354 | self.assertEqual(('1.2.3.4', 80), con.sock.sa) |
|
355 | self.assertEqual(('1.2.3.4', 80), con.sock.sa) | |
355 | self.assertEqual(expected_req, con.sock.sent) |
|
356 | self.assertEqual(expected_req, con.sock.sent) | |
|
357 | ||||
|
358 | def test_conn_keep_alive_but_server_close_anyway(self): | |||
|
359 | sockets = [] | |||
|
360 | def closingsocket(*args, **kwargs): | |||
|
361 | s = util.MockSocket(*args, **kwargs) | |||
|
362 | sockets.append(s) | |||
|
363 | s.data = ['HTTP/1.1 200 OK\r\n', | |||
|
364 | 'Server: BogusServer 1.0\r\n', | |||
|
365 | 'Connection: Keep-Alive\r\n', | |||
|
366 | 'Content-Length: 16', | |||
|
367 | '\r\n\r\n', | |||
|
368 | 'You can do that.'] | |||
|
369 | s.close_on_empty = True | |||
|
370 | return s | |||
|
371 | ||||
|
372 | socket.socket = closingsocket | |||
|
373 | con = http.HTTPConnection('1.2.3.4:80') | |||
|
374 | con._connect() | |||
|
375 | con.request('GET', '/') | |||
|
376 | r1 = con.getresponse() | |||
|
377 | r1.read() | |||
|
378 | self.assertFalse(con.sock.closed) | |||
|
379 | self.assert_(con.sock.remote_closed) | |||
|
380 | con.request('GET', '/') | |||
|
381 | self.assertEqual(2, len(sockets)) | |||
|
382 | ||||
|
383 | def test_no_response_raises_response_not_ready(self): | |||
|
384 | con = http.HTTPConnection('foo') | |||
|
385 | self.assertRaises(http.httplib.ResponseNotReady, con.getresponse) | |||
356 | # no-check-code |
|
386 | # no-check-code |
@@ -53,7 +53,7 b' class ChunkedTransferTest(util.HttpTestB' | |||||
53 | con = http.HTTPConnection('1.2.3.4:80') |
|
53 | con = http.HTTPConnection('1.2.3.4:80') | |
54 | con._connect() |
|
54 | con._connect() | |
55 | sock = con.sock |
|
55 | sock = con.sock | |
56 |
sock.read_wait_sentinel = ' |
|
56 | sock.read_wait_sentinel = '0\r\n\r\n' | |
57 | sock.data = ['HTTP/1.1 200 OK\r\n', |
|
57 | sock.data = ['HTTP/1.1 200 OK\r\n', | |
58 | 'Server: BogusServer 1.0\r\n', |
|
58 | 'Server: BogusServer 1.0\r\n', | |
59 | 'Content-Length: 6', |
|
59 | 'Content-Length: 6', |
@@ -43,7 +43,7 b' def make_preloaded_socket(data):' | |||||
43 | """ |
|
43 | """ | |
44 | def s(*args, **kwargs): |
|
44 | def s(*args, **kwargs): | |
45 | sock = util.MockSocket(*args, **kwargs) |
|
45 | sock = util.MockSocket(*args, **kwargs) | |
46 | sock.data = data[:] |
|
46 | sock.early_data = data[:] | |
47 | return sock |
|
47 | return sock | |
48 | return s |
|
48 | return s | |
49 |
|
49 | |||
@@ -97,24 +97,27 b' class ProxyHttpTest(util.HttpTestBase, u' | |||||
97 | '\r\n' |
|
97 | '\r\n' | |
98 | '1234567890']) |
|
98 | '1234567890']) | |
99 | con._connect() |
|
99 | con._connect() | |
100 |
con.sock.data |
|
100 | con.sock.data = ['HTTP/1.1 200 OK\r\n', | |
101 |
|
|
101 | 'Server: BogusServer 1.0\r\n', | |
102 |
|
|
102 | 'Content-Length: 10\r\n', | |
103 |
|
|
103 | '\r\n' | |
104 |
|
|
104 | '1234567890' | |
105 |
|
|
105 | ] | |
|
106 | connect_sent = con.sock.sent | |||
|
107 | con.sock.sent = '' | |||
106 | con.request('GET', '/') |
|
108 | con.request('GET', '/') | |
107 |
|
109 | |||
108 |
expected_ |
|
110 | expected_connect = ('CONNECT 1.2.3.4:443 HTTP/1.0\r\n' | |
109 | 'Host: 1.2.3.4\r\n' |
|
111 | 'Host: 1.2.3.4\r\n' | |
110 | 'accept-encoding: identity\r\n' |
|
112 | 'accept-encoding: identity\r\n' | |
111 | '\r\n' |
|
113 | '\r\n') | |
112 |
|
|
114 | expected_request = ('GET / HTTP/1.1\r\n' | |
113 | 'Host: 1.2.3.4\r\n' |
|
115 | 'Host: 1.2.3.4\r\n' | |
114 | 'accept-encoding: identity\r\n\r\n') |
|
116 | 'accept-encoding: identity\r\n\r\n') | |
115 |
|
117 | |||
116 | self.assertEqual(('127.0.0.42', 4242), con.sock.sa) |
|
118 | self.assertEqual(('127.0.0.42', 4242), con.sock.sa) | |
117 |
self.assertStringEqual(expected_ |
|
119 | self.assertStringEqual(expected_connect, connect_sent) | |
|
120 | self.assertStringEqual(expected_request, con.sock.sent) | |||
118 | resp = con.getresponse() |
|
121 | resp = con.getresponse() | |
119 | self.assertEqual(resp.status, 200) |
|
122 | self.assertEqual(resp.status, 200) | |
120 | self.assertEqual('1234567890', resp.read()) |
|
123 | self.assertEqual('1234567890', resp.read()) |
@@ -41,15 +41,15 b' class HttpSslTest(util.HttpTestBase, uni' | |||||
41 | con._connect() |
|
41 | con._connect() | |
42 | # extend the list instead of assign because of how |
|
42 | # extend the list instead of assign because of how | |
43 | # MockSSLSocket works. |
|
43 | # MockSSLSocket works. | |
44 |
con.sock.data |
|
44 | con.sock.data = ['HTTP/1.1 200 OK\r\n', | |
45 |
|
|
45 | 'Server: BogusServer 1.0\r\n', | |
46 |
|
|
46 | 'MultiHeader: Value\r\n' | |
47 |
|
|
47 | 'MultiHeader: Other Value\r\n' | |
48 |
|
|
48 | 'MultiHeader: One More!\r\n' | |
49 |
|
|
49 | 'Content-Length: 10\r\n', | |
50 |
|
|
50 | '\r\n' | |
51 |
|
|
51 | '1234567890' | |
52 |
|
|
52 | ] | |
53 | con.request('GET', '/') |
|
53 | con.request('GET', '/') | |
54 |
|
54 | |||
55 | expected_req = ('GET / HTTP/1.1\r\n' |
|
55 | expected_req = ('GET / HTTP/1.1\r\n' | |
@@ -68,17 +68,15 b' class HttpSslTest(util.HttpTestBase, uni' | |||||
68 | def testSslRereadInEarlyResponse(self): |
|
68 | def testSslRereadInEarlyResponse(self): | |
69 | con = http.HTTPConnection('1.2.3.4:443') |
|
69 | con = http.HTTPConnection('1.2.3.4:443') | |
70 | con._connect() |
|
70 | con._connect() | |
71 | # extend the list instead of assign because of how |
|
71 | con.sock.early_data = ['HTTP/1.1 200 OK\r\n', | |
72 | # MockSSLSocket works. |
|
72 | 'Server: BogusServer 1.0\r\n', | |
73 | con.sock.early_data.extend(['HTTP/1.1 200 OK\r\n', |
|
73 | 'MultiHeader: Value\r\n' | |
74 |
|
|
74 | 'MultiHeader: Other Value\r\n' | |
75 |
|
|
75 | 'MultiHeader: One More!\r\n' | |
76 |
|
|
76 | 'Content-Length: 10\r\n', | |
77 |
|
|
77 | '\r\n' | |
78 |
|
|
78 | '1234567890' | |
79 |
|
|
79 | ] | |
80 | '1234567890' |
|
|||
81 | ]) |
|
|||
82 |
|
80 | |||
83 | expected_req = self.doPost(con, False) |
|
81 | expected_req = self.doPost(con, False) | |
84 | self.assertEqual(None, con.sock, |
|
82 | self.assertEqual(None, con.sock, | |
@@ -92,3 +90,4 b' class HttpSslTest(util.HttpTestBase, uni' | |||||
92 | resp.headers.getheaders('multiheader')) |
|
90 | resp.headers.getheaders('multiheader')) | |
93 | self.assertEqual(['BogusServer 1.0'], |
|
91 | self.assertEqual(['BogusServer 1.0'], | |
94 | resp.headers.getheaders('server')) |
|
92 | resp.headers.getheaders('server')) | |
|
93 | # no-check-code |
@@ -88,7 +88,7 b' class MockSocket(object):' | |||||
88 | def ready_for_read(self): |
|
88 | def ready_for_read(self): | |
89 | return ((self.early_data and http._END_HEADERS in self.sent) |
|
89 | return ((self.early_data and http._END_HEADERS in self.sent) | |
90 | or (self.read_wait_sentinel in self.sent and self.data) |
|
90 | or (self.read_wait_sentinel in self.sent and self.data) | |
91 | or self.closed) |
|
91 | or self.closed or self.remote_closed) | |
92 |
|
92 | |||
93 | def send(self, data): |
|
93 | def send(self, data): | |
94 | # this is a horrible mock, but nothing needs us to raise the |
|
94 | # this is a horrible mock, but nothing needs us to raise the | |
@@ -117,6 +117,11 b' class MockSSLSocket(object):' | |||||
117 | def __getattr__(self, key): |
|
117 | def __getattr__(self, key): | |
118 | return getattr(self._sock, key) |
|
118 | return getattr(self._sock, key) | |
119 |
|
119 | |||
|
120 | def __setattr__(self, key, value): | |||
|
121 | if key not in ('_sock', '_fail_recv'): | |||
|
122 | return setattr(self._sock, key, value) | |||
|
123 | return object.__setattr__(self, key, value) | |||
|
124 | ||||
120 | def recv(self, amt=-1): |
|
125 | def recv(self, amt=-1): | |
121 | try: |
|
126 | try: | |
122 | if self._fail_recv: |
|
127 | if self._fail_recv: |
General Comments 0
You need to be logged in to leave comments.
Login now