--- httplib.py 2014-05-20 15:04:20.360809000 +1200 +++ httplib_fix.py 2014-05-20 15:06:54.081987000 +1200 @@ -68,6 +68,7 @@ Req-sent-unread-response _CS_REQ_S from array import array import socket +import errno from sys import py3kwarning from urlparse import urlsplit import warnings @@ -211,6 +212,83 @@ responses = { # maximal amount of data to read at one time in _safe_read MAXAMOUNT = 1048576 +def eintr_read(fp, size=-1): + # size < 0 read until EOF or error + # size > 0 read until size == 0 + # size == 0 ? + + if size < 0: + bufsize = 8192 + else: + bufsize = size + + block = '' + while bufsize > 0: + try: + if hasattr(fp, '_sock'): + chunk = fp._sock.recv(bufsize) + else: + chunk = fp.read(bufsize) + except socket.error, msg: + if msg.errno == errno.EINTR: + continue + raise + + # EOF ? + if chunk == '': + break + + # Continue until EOF + if size < 0: + continue + + # Otherwise + block += chunk + bufsize -= len(chunk) + + return block + +def eintr_readline(fp): + line = '' + while True: + c = eintr_read(fp, 1) + + # EOF ? + if c == '': + break + + line += c + + # EOL ? + if c == '\n': + break + + return line + +def eintr_readlines(fp, sizehint=0): + total = 0 + list = [] + while True: + line = eintr_readline(fp) + if not line: + break + list.append(line) + total += len(line) + if sizehint and total >= sizehint: + break + return list + +def eintr_sendall(sock, data): + while data: + try: + size = sock.send(data) + except socket.error, msg: + if msg.errno == errno.EINTR: + continue + raise + data = data[size:] + return None + class HTTPMessage(mimetools.Message): def addheader(self, key, value): @@ -273,7 +351,7 @@ class HTTPMessage(mimetools.Message): except IOError: startofline = tell = None self.seekable = 0 - line = self.fp.readline() + line = eintr_readline(self.fp) if not line: self.status = 'EOF in headers' break @@ -346,7 +424,7 @@ class HTTPResponse: def _read_status(self): # Initialize with Simple-Response defaults - line = self.fp.readline() + line = eintr_readline(self.fp) if self.debuglevel > 0: print "reply:", repr(line) if not line: @@ -393,7 +471,7 @@ class HTTPResponse: break # skip the header from the 100 response while True: - skip = self.fp.readline().strip() + skip = eintr_readline(self.fp).strip() if not skip: break if self.debuglevel > 0: @@ -496,7 +574,11 @@ class HTTPResponse: def close(self): if self.fp: - self.fp.close() + try: + self.fp.close() + except socket.error, msg: + if msg.errno != errno.EINTR: + raise self.fp = None def isclosed(self): @@ -520,7 +602,7 @@ class HTTPResponse: if amt is None: # unbounded read if self.length is None: - s = self.fp.read() + s = eintr_read(self.fp) else: s = self._safe_read(self.length) self.length = 0 @@ -535,7 +617,7 @@ class HTTPResponse: # we do not use _safe_read() here because this may be a .will_close # connection, and the user is reading more bytes than will be provided # (for example, reading in 1k chunks) - s = self.fp.read(amt) + s = eintr_read(self.fp, amt) if self.length is not None: self.length -= len(s) if not self.length: @@ -548,7 +630,7 @@ class HTTPResponse: value = [] while True: if chunk_left is None: - line = self.fp.readline() + line = eintr_readline(self.fp) i = line.find(';') if i >= 0: line = line[:i] # strip chunk-extensions @@ -583,7 +665,7 @@ class HTTPResponse: # read and discard trailer up to the CRLF terminator ### note: we shouldn't have any trailers! while True: - line = self.fp.readline() + line = eintr_readline(self.fp) if not line: # a vanishingly small number of sites EOF without # sending the trailer @@ -612,7 +694,7 @@ class HTTPResponse: """ s = [] while amt > 0: - chunk = self.fp.read(min(amt, MAXAMOUNT)) + chunk = eintr_read(self.fp, min(amt, MAXAMOUNT)) if not chunk: raise IncompleteRead(''.join(s), amt) s.append(chunk) @@ -694,14 +776,21 @@ class HTTPConnection: raise socket.error, "Tunnel connection failed: %d %s" % (code, message.strip()) while True: - line = response.fp.readline() + line = eintr_readline(response.fp) if line == '\r\n': break def connect(self): """Connect to the host and port specified in __init__.""" - self.sock = socket.create_connection((self.host,self.port), - self.timeout) + while 1: + try: + self.sock = socket.create_connection((self.host,self.port), + self.timeout) + except socket.error, msg: + if msg.errno == errno.EINTR: + continue + raise + break if self._tunnel_host: self._tunnel() @@ -709,7 +798,11 @@ class HTTPConnection: def close(self): """Close the connection to the HTTP server.""" if self.sock: - self.sock.close() # close it manually... there may be other refs + try: + self.sock.close() # close it manually... there may be other refs + except socket.error, msg: + if msg.errno != errno.EINTR: + raise self.sock = None if self.__response: self.__response.close() @@ -737,10 +830,10 @@ class HTTPConnection: if self.debuglevel > 0: print "sendIng a read()able" data=str.read(blocksize) while data: - self.sock.sendall(data) + eintr_sendall(self.sock, data) data=str.read(blocksize) else: - self.sock.sendall(str) + eintr_sendall(self.sock, str) except socket.error, v: if v[0] == 32: # Broken pipe self.close() @@ -1093,7 +1186,14 @@ else: def connect(self): "Connect to a host on a given (SSL) port." - sock = socket.create_connection((self.host, self.port), self.timeout) + while 1: + try: + sock = socket.create_connection((self.host, self.port), self.timeout) + except socket.error, msg: + if msg.errno == errno.EINTR: + continue + raise + break if self._tunnel_host: self.sock = sock self._tunnel() @@ -1219,15 +1319,15 @@ class LineAndFileWrapper: def read(self, amt=None): if self._line_consumed: - return self._file.read(amt) + return eintr_read(self._file, amt) assert self._line_left if amt is None or amt > self._line_left: s = self._line[self._line_offset:] self._done() if amt is None: - return s + self._file.read() + return s + eintr_read(self._file) else: - return s + self._file.read(amt - len(s)) + return s + eintr_read(self._file, amt - len(s)) else: assert amt <= self._line_left i = self._line_offset @@ -1241,7 +1341,7 @@ class LineAndFileWrapper: def readline(self): if self._line_consumed: - return self._file.readline() + return eintr_readline(self._file) assert self._line_left s = self._line[self._line_offset:] self._done() @@ -1249,14 +1349,14 @@ class LineAndFileWrapper: def readlines(self, size=None): if self._line_consumed: - return self._file.readlines(size) + return eintr_readlines(self._file, size) assert self._line_left L = [self._line[self._line_offset:]] self._done() if size is None: - return L + self._file.readlines() + return L + eintr_readlines(self._file) else: - return L + self._file.readlines(size) + return L + eintr_readlines(self._file, size) def test(): """Test this module.