##// END OF EJS Templates
httpclient: import revision fc731618702a of py-nonblocking-http
Augie Fackler -
r14376:a75e0f4b default
parent child Browse files
Show More
@@ -112,6 +112,10 b' class HTTPResponse(object):'
112 112
113 113 def complete(self):
114 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 120 if self._chunked:
117 121 return self._chunked_done
@@ -174,10 +178,7 b' class HTTPResponse(object):'
174 178 return True
175 179 logger.debug('response read %d data during _select', len(data))
176 180 if not data:
177 if not self.headers:
178 self._load_response(self._end_headers)
179 self._content_len = 0
180 elif self._content_len == _LEN_CLOSE_IS_END:
181 if self.headers and self._content_len == _LEN_CLOSE_IS_END:
181 182 self._content_len = len(self._body)
182 183 return False
183 184 else:
@@ -561,17 +562,46 b' class HTTPConnection(object):'
561 562 continue
562 563 if not data:
563 564 logger.info('socket appears closed in read')
564 outgoing_headers = body = None
565 break
565 self.sock = None
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 599 if response is None:
567 600 response = self.response_class(r[0], self.timeout)
568 601 response._load_response(data)
569 if (response._content_len == _LEN_CLOSE_IS_END
570 and len(data) == 0):
571 response._content_len = len(response._body)
572 if response.complete():
573 w = []
574 response.will_close = True
602 # Jump to the next select() call so we load more
603 # data if the server is still sending us content.
604 continue
575 605 except socket.error, e:
576 606 if e[0] != errno.EPIPE and not was_first:
577 607 raise
@@ -662,4 +692,7 b' class BadRequestData(httplib.HTTPExcepti'
662 692
663 693 class HTTPProxyConnectFailedException(httplib.HTTPException):
664 694 """Connecting to the HTTP proxy failed."""
695
696 class HTTPStateError(httplib.HTTPException):
697 """Invalid internal state encountered."""
665 698 # no-check-code
@@ -26,6 +26,7 b''
26 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 import socket
29 30 import unittest
30 31
31 32 import http
@@ -39,7 +40,7 b' class SimpleHttpTest(util.HttpTestBase, '
39 40 def _run_simple_test(self, host, server_data, expected_req, expected_data):
40 41 con = http.HTTPConnection(host)
41 42 con._connect()
42 con.sock.data.extend(server_data)
43 con.sock.data = server_data
43 44 con.request('GET', '/')
44 45
45 46 self.assertStringEqual(expected_req, con.sock.sent)
@@ -353,4 +354,33 b' dotencode'
353 354
354 355 self.assertEqual(('1.2.3.4', 80), con.sock.sa)
355 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 386 # no-check-code
@@ -53,7 +53,7 b' class ChunkedTransferTest(util.HttpTestB'
53 53 con = http.HTTPConnection('1.2.3.4:80')
54 54 con._connect()
55 55 sock = con.sock
56 sock.read_wait_sentinel = 'end-of-body'
56 sock.read_wait_sentinel = '0\r\n\r\n'
57 57 sock.data = ['HTTP/1.1 200 OK\r\n',
58 58 'Server: BogusServer 1.0\r\n',
59 59 'Content-Length: 6',
@@ -43,7 +43,7 b' def make_preloaded_socket(data):'
43 43 """
44 44 def s(*args, **kwargs):
45 45 sock = util.MockSocket(*args, **kwargs)
46 sock.data = data[:]
46 sock.early_data = data[:]
47 47 return sock
48 48 return s
49 49
@@ -97,24 +97,27 b' class ProxyHttpTest(util.HttpTestBase, u'
97 97 '\r\n'
98 98 '1234567890'])
99 99 con._connect()
100 con.sock.data.extend(['HTTP/1.1 200 OK\r\n',
101 'Server: BogusServer 1.0\r\n',
102 'Content-Length: 10\r\n',
103 '\r\n'
104 '1234567890'
105 ])
100 con.sock.data = ['HTTP/1.1 200 OK\r\n',
101 'Server: BogusServer 1.0\r\n',
102 'Content-Length: 10\r\n',
103 '\r\n'
104 '1234567890'
105 ]
106 connect_sent = con.sock.sent
107 con.sock.sent = ''
106 108 con.request('GET', '/')
107 109
108 expected_req = ('CONNECT 1.2.3.4:443 HTTP/1.0\r\n'
109 'Host: 1.2.3.4\r\n'
110 'accept-encoding: identity\r\n'
111 '\r\n'
112 'GET / HTTP/1.1\r\n'
113 'Host: 1.2.3.4\r\n'
114 'accept-encoding: identity\r\n\r\n')
110 expected_connect = ('CONNECT 1.2.3.4:443 HTTP/1.0\r\n'
111 'Host: 1.2.3.4\r\n'
112 'accept-encoding: identity\r\n'
113 '\r\n')
114 expected_request = ('GET / HTTP/1.1\r\n'
115 'Host: 1.2.3.4\r\n'
116 'accept-encoding: identity\r\n\r\n')
115 117
116 118 self.assertEqual(('127.0.0.42', 4242), con.sock.sa)
117 self.assertStringEqual(expected_req, con.sock.sent)
119 self.assertStringEqual(expected_connect, connect_sent)
120 self.assertStringEqual(expected_request, con.sock.sent)
118 121 resp = con.getresponse()
119 122 self.assertEqual(resp.status, 200)
120 123 self.assertEqual('1234567890', resp.read())
@@ -41,15 +41,15 b' class HttpSslTest(util.HttpTestBase, uni'
41 41 con._connect()
42 42 # extend the list instead of assign because of how
43 43 # MockSSLSocket works.
44 con.sock.data.extend(['HTTP/1.1 200 OK\r\n',
45 'Server: BogusServer 1.0\r\n',
46 'MultiHeader: Value\r\n'
47 'MultiHeader: Other Value\r\n'
48 'MultiHeader: One More!\r\n'
49 'Content-Length: 10\r\n',
50 '\r\n'
51 '1234567890'
52 ])
44 con.sock.data = ['HTTP/1.1 200 OK\r\n',
45 'Server: BogusServer 1.0\r\n',
46 'MultiHeader: Value\r\n'
47 'MultiHeader: Other Value\r\n'
48 'MultiHeader: One More!\r\n'
49 'Content-Length: 10\r\n',
50 '\r\n'
51 '1234567890'
52 ]
53 53 con.request('GET', '/')
54 54
55 55 expected_req = ('GET / HTTP/1.1\r\n'
@@ -68,17 +68,15 b' class HttpSslTest(util.HttpTestBase, uni'
68 68 def testSslRereadInEarlyResponse(self):
69 69 con = http.HTTPConnection('1.2.3.4:443')
70 70 con._connect()
71 # extend the list instead of assign because of how
72 # MockSSLSocket works.
73 con.sock.early_data.extend(['HTTP/1.1 200 OK\r\n',
74 'Server: BogusServer 1.0\r\n',
75 'MultiHeader: Value\r\n'
76 'MultiHeader: Other Value\r\n'
77 'MultiHeader: One More!\r\n'
78 'Content-Length: 10\r\n',
79 '\r\n'
80 '1234567890'
81 ])
71 con.sock.early_data = ['HTTP/1.1 200 OK\r\n',
72 'Server: BogusServer 1.0\r\n',
73 'MultiHeader: Value\r\n'
74 'MultiHeader: Other Value\r\n'
75 'MultiHeader: One More!\r\n'
76 'Content-Length: 10\r\n',
77 '\r\n'
78 '1234567890'
79 ]
82 80
83 81 expected_req = self.doPost(con, False)
84 82 self.assertEqual(None, con.sock,
@@ -92,3 +90,4 b' class HttpSslTest(util.HttpTestBase, uni'
92 90 resp.headers.getheaders('multiheader'))
93 91 self.assertEqual(['BogusServer 1.0'],
94 92 resp.headers.getheaders('server'))
93 # no-check-code
@@ -88,7 +88,7 b' class MockSocket(object):'
88 88 def ready_for_read(self):
89 89 return ((self.early_data and http._END_HEADERS in self.sent)
90 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 93 def send(self, data):
94 94 # this is a horrible mock, but nothing needs us to raise the
@@ -117,6 +117,11 b' class MockSSLSocket(object):'
117 117 def __getattr__(self, key):
118 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 125 def recv(self, amt=-1):
121 126 try:
122 127 if self._fail_recv:
General Comments 0
You need to be logged in to leave comments. Login now