##// 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 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 outgoing_headers = body = None
565 self.sock = None
565 break
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.extend(server_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 = 'end-of-body'
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.extend(['HTTP/1.1 200 OK\r\n',
100 con.sock.data = ['HTTP/1.1 200 OK\r\n',
101 'Server: BogusServer 1.0\r\n',
101 'Server: BogusServer 1.0\r\n',
102 'Content-Length: 10\r\n',
102 'Content-Length: 10\r\n',
103 '\r\n'
103 '\r\n'
104 '1234567890'
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_req = ('CONNECT 1.2.3.4:443 HTTP/1.0\r\n'
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 'GET / HTTP/1.1\r\n'
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_req, con.sock.sent)
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.extend(['HTTP/1.1 200 OK\r\n',
44 con.sock.data = ['HTTP/1.1 200 OK\r\n',
45 'Server: BogusServer 1.0\r\n',
45 'Server: BogusServer 1.0\r\n',
46 'MultiHeader: Value\r\n'
46 'MultiHeader: Value\r\n'
47 'MultiHeader: Other Value\r\n'
47 'MultiHeader: Other Value\r\n'
48 'MultiHeader: One More!\r\n'
48 'MultiHeader: One More!\r\n'
49 'Content-Length: 10\r\n',
49 'Content-Length: 10\r\n',
50 '\r\n'
50 '\r\n'
51 '1234567890'
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 'Server: BogusServer 1.0\r\n',
74 'MultiHeader: Other Value\r\n'
75 'MultiHeader: Value\r\n'
75 'MultiHeader: One More!\r\n'
76 'MultiHeader: Other Value\r\n'
76 'Content-Length: 10\r\n',
77 'MultiHeader: One More!\r\n'
77 '\r\n'
78 'Content-Length: 10\r\n',
78 '1234567890'
79 '\r\n'
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