[PATCH] httpclient: update to 07d8c356f4d1 of py-nonblocking-http

Augie Fackler durin42 at gmail.com
Mon Oct 10 17:57:49 CDT 2011


# HG changeset patch
# User Augie Fackler <durin42 at gmail.com>
# Date 1318287460 18000
# Node ID 690ae3afd04d88a46a18e85ba189bf11c58bf1bf
# Parent  231aac5280baae0614d7f278bb306b95c4a1488b
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.

diff --git a/mercurial/httpclient/__init__.py b/mercurial/httpclient/__init__.py
--- a/mercurial/httpclient/__init__.py
+++ b/mercurial/httpclient/__init__.py
@@ -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
diff --git a/mercurial/httpclient/tests/simple_http_test.py b/mercurial/httpclient/tests/simple_http_test.py
--- a/mercurial/httpclient/tests/simple_http_test.py
+++ b/mercurial/httpclient/tests/simple_http_test.py
@@ -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)
diff --git a/mercurial/httpclient/tests/test_chunked_transfer.py b/mercurial/httpclient/tests/test_chunked_transfer.py
--- a/mercurial/httpclient/tests/test_chunked_transfer.py
+++ b/mercurial/httpclient/tests/test_chunked_transfer.py
@@ -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


More information about the Mercurial-devel mailing list