Commit 2c6aee90 authored by Victor Stinner's avatar Victor Stinner

Forward port r70643 (#5542) + part of r83120 (just remove the comment)

Remove special logic that closes HTTPConnection socket on EPIPE.

If the socket is closed, the client has no chance to read the response
from the server.  EPIPE means that it isn't possible to write more
data from the socket, but not that it is impossible to read.
parent 4c16d122
...@@ -67,6 +67,7 @@ Req-sent-unread-response _CS_REQ_SENT <response_class> ...@@ -67,6 +67,7 @@ Req-sent-unread-response _CS_REQ_SENT <response_class>
""" """
from array import array from array import array
import os
import socket import socket
from sys import py3kwarning from sys import py3kwarning
from urlparse import urlsplit from urlparse import urlsplit
...@@ -756,27 +757,17 @@ class HTTPConnection: ...@@ -756,27 +757,17 @@ class HTTPConnection:
else: else:
raise NotConnected() raise NotConnected()
# send the data to the server. if we get a broken pipe, then close
# the socket. we want to reconnect when somebody tries to send again.
#
# NOTE: we DO propagate the error, though, because we cannot simply
# ignore the error... the caller will know if they can retry.
if self.debuglevel > 0: if self.debuglevel > 0:
print "send:", repr(str) print "send:", repr(str)
try: blocksize = 8192
blocksize=8192 if hasattr(str,'read') and not isinstance(str, array):
if hasattr(str,'read') and not isinstance(str, array): if self.debuglevel > 0: print "sendIng a read()able"
if self.debuglevel > 0: print "sendIng a read()able" data = str.read(blocksize)
data=str.read(blocksize) while data:
while data: self.sock.sendall(data)
self.sock.sendall(data) data = str.read(blocksize)
data=str.read(blocksize) else:
else: self.sock.sendall(str)
self.sock.sendall(str)
except socket.error, v:
if v.args[0] == 32: # Broken pipe
self.close()
raise
def _output(self, s): def _output(self, s):
"""Add a line of output to the current request buffer. """Add a line of output to the current request buffer.
...@@ -941,15 +932,7 @@ class HTTPConnection: ...@@ -941,15 +932,7 @@ class HTTPConnection:
def request(self, method, url, body=None, headers={}): def request(self, method, url, body=None, headers={}):
"""Send a complete request to the server.""" """Send a complete request to the server."""
self._send_request(method, url, body, headers)
try:
self._send_request(method, url, body, headers)
except socket.error, v:
# trap 'Broken pipe' if we're allowed to automatically reconnect
if v.args[0] != 32 or not self.auto_open:
raise
# try one more time
self._send_request(method, url, body, headers)
def _set_content_length(self, body): def _set_content_length(self, body):
# Set the content-length based on the body. # Set the content-length based on the body.
...@@ -959,7 +942,6 @@ class HTTPConnection: ...@@ -959,7 +942,6 @@ class HTTPConnection:
except TypeError, te: except TypeError, te:
# If this is a file-like object, try to # If this is a file-like object, try to
# fstat its file descriptor # fstat its file descriptor
import os
try: try:
thelen = str(os.fstat(body.fileno()).st_size) thelen = str(os.fstat(body.fileno()).st_size)
except (AttributeError, OSError): except (AttributeError, OSError):
...@@ -970,7 +952,7 @@ class HTTPConnection: ...@@ -970,7 +952,7 @@ class HTTPConnection:
self.putheader('Content-Length', thelen) self.putheader('Content-Length', thelen)
def _send_request(self, method, url, body, headers): def _send_request(self, method, url, body, headers):
# honour explicitly requested Host: and Accept-Encoding headers # Honor explicitly requested Host: and Accept-Encoding: headers.
header_names = dict.fromkeys([k.lower() for k in headers]) header_names = dict.fromkeys([k.lower() for k in headers])
skips = {} skips = {}
if 'host' in header_names: if 'host' in header_names:
......
...@@ -2,6 +2,7 @@ import array ...@@ -2,6 +2,7 @@ import array
import httplib import httplib
import StringIO import StringIO
import socket import socket
import errno
import unittest import unittest
TestCase = unittest.TestCase TestCase = unittest.TestCase
...@@ -24,6 +25,21 @@ class FakeSocket: ...@@ -24,6 +25,21 @@ class FakeSocket:
raise httplib.UnimplementedFileMode() raise httplib.UnimplementedFileMode()
return self.fileclass(self.text) return self.fileclass(self.text)
class EPipeSocket(FakeSocket):
def __init__(self, text, pipe_trigger):
# When sendall() is called with pipe_trigger, raise EPIPE.
FakeSocket.__init__(self, text)
self.pipe_trigger = pipe_trigger
def sendall(self, data):
if self.pipe_trigger in data:
raise socket.error(errno.EPIPE, "gotcha")
self.data += data
def close(self):
pass
class NoEOFStringIO(StringIO.StringIO): class NoEOFStringIO(StringIO.StringIO):
"""Like StringIO, but raises AssertionError on EOF. """Like StringIO, but raises AssertionError on EOF.
...@@ -254,6 +270,21 @@ class BasicTest(TestCase): ...@@ -254,6 +270,21 @@ class BasicTest(TestCase):
finally: finally:
resp.close() resp.close()
def test_epipe(self):
sock = EPipeSocket(
"HTTP/1.0 401 Authorization Required\r\n"
"Content-type: text/html\r\n"
"WWW-Authenticate: Basic realm=\"example\"\r\n",
b"Content-Length")
conn = httplib.HTTPConnection("example.com")
conn.sock = sock
self.assertRaises(socket.error,
lambda: conn.request("PUT", "/url", "body"))
resp = conn.getresponse()
self.assertEqual(401, resp.status)
self.assertEqual("Basic realm=\"example\"",
resp.getheader("www-authenticate"))
class OfflineTest(TestCase): class OfflineTest(TestCase):
def test_responses(self): def test_responses(self):
......
...@@ -18,6 +18,8 @@ Core and Builtins ...@@ -18,6 +18,8 @@ Core and Builtins
Library Library
------- -------
- Issue #5542: Remove special logic that closes HTTPConnection socket on EPIPE.
- Issue #4629: getopt raises an error if an argument ends with = whereas getopt - Issue #4629: getopt raises an error if an argument ends with = whereas getopt
doesn't except a value (eg. --help= is rejected if getopt uses ['help='] long doesn't except a value (eg. --help= is rejected if getopt uses ['help='] long
options). options).
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment