httpclient: update to
07d8c356f4d1 of py-nonblocking-http
This addresses a defect when the server closes the socket before
finishing a response (if it crashes, for example) first spotted in
Issue2951.
--- a/mercurial/httpclient/__init__.py Mon Oct 10 13:52:54 2011 +0200
+++ b/mercurial/httpclient/__init__.py Mon Oct 10 17:57:40 2011 -0500
@@ -171,6 +171,14 @@
logger.info('cl: %r body: %r', self._content_len, self._body)
try:
data = self.sock.recv(INCOMING_BUFFER_SIZE)
+ # If the socket was readable and no data was read, that
+ # means the socket was closed. If this isn't a
+ # _CLOSE_IS_END socket, then something is wrong if we're
+ # here (we shouldn't enter _select() if the response is
+ # complete), so abort.
+ if not data and self._content_len != _LEN_CLOSE_IS_END:
+ raise HTTPRemoteClosedError(
+ 'server appears to have closed the socket mid-response')
except socket.sslerror, e:
if e.args[0] != socket.SSL_ERROR_WANT_READ:
raise
@@ -693,6 +701,11 @@
class HTTPProxyConnectFailedException(httplib.HTTPException):
"""Connecting to the HTTP proxy failed."""
+
class HTTPStateError(httplib.HTTPException):
"""Invalid internal state encountered."""
+
+
+class HTTPRemoteClosedError(httplib.HTTPException):
+ """The server closed the remote socket in the middle of a response."""
# no-check-code
--- a/mercurial/httpclient/tests/simple_http_test.py Mon Oct 10 13:52:54 2011 +0200
+++ b/mercurial/httpclient/tests/simple_http_test.py Mon Oct 10 17:57:40 2011 -0500
@@ -380,6 +380,21 @@
con.request('GET', '/')
self.assertEqual(2, len(sockets))
+ def test_server_closes_before_end_of_body(self):
+ con = http.HTTPConnection('1.2.3.4:80')
+ con._connect()
+ s = con.sock
+ s.data = ['HTTP/1.1 200 OK\r\n',
+ 'Server: BogusServer 1.0\r\n',
+ 'Connection: Keep-Alive\r\n',
+ 'Content-Length: 16',
+ '\r\n\r\n',
+ 'You can '] # Note: this is shorter than content-length
+ s.close_on_empty = True
+ con.request('GET', '/')
+ r1 = con.getresponse()
+ self.assertRaises(http.HTTPRemoteClosedError, r1.read)
+
def test_no_response_raises_response_not_ready(self):
con = http.HTTPConnection('foo')
self.assertRaises(http.httplib.ResponseNotReady, con.getresponse)
--- a/mercurial/httpclient/tests/test_chunked_transfer.py Mon Oct 10 13:52:54 2011 +0200
+++ b/mercurial/httpclient/tests/test_chunked_transfer.py Mon Oct 10 17:57:40 2011 -0500
@@ -134,4 +134,20 @@
con.request('GET', '/')
self.assertStringEqual('hi there\nthere\nthere\nthere\nthere\n',
con.getresponse().read())
+
+ def testChunkedDownloadEarlyHangup(self):
+ con = http.HTTPConnection('1.2.3.4:80')
+ con._connect()
+ sock = con.sock
+ broken = chunkedblock('hi'*20)[:-1]
+ sock.data = ['HTTP/1.1 200 OK\r\n',
+ 'Server: BogusServer 1.0\r\n',
+ 'transfer-encoding: chunked',
+ '\r\n\r\n',
+ broken,
+ ]
+ sock.close_on_empty = True
+ con.request('GET', '/')
+ resp = con.getresponse()
+ self.assertRaises(http.HTTPRemoteClosedError, resp.read)
# no-check-code