diff -r 031fc0231f3d Lib/http/client.py --- a/Lib/http/client.py Thu Jan 15 22:53:21 2015 +0100 +++ b/Lib/http/client.py Thu Jan 22 09:01:12 2015 -0800 @@ -681,13 +681,14 @@ default_port = HTTP_PORT auto_open = 1 debuglevel = 0 - # TCP Maximum Segment Size (MSS) is determined by the TCP stack on - # a per-connection basis. There is no simple and efficient - # platform independent mechanism for determining the MSS, so - # instead a reasonable estimate is chosen. The getsockopt() - # interface using the TCP_MAXSEG parameter may be a suitable - # approach on some operating systems. A value of 16KiB is chosen - # as a reasonable estimate of the maximum MSS. + + # TCP Maximum Segment Size (MSS) is determined by the TCP stack on a + # per-connection basis. A default value of 16384 is used for systems that + # don't support the TCP_MAXSEG option in [get|set]sockopt. For systems that + # do support it, this will be overridden in connect() as the MSS is + # determined when the connection is established. + # + # More details can be found at: https://tools.ietf.org/html/rfc6691 mss = 16384 def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, @@ -786,8 +787,16 @@ def connect(self): """Connect to the host and port specified in __init__.""" - self.sock = self._create_connection((self.host,self.port), - self.timeout, self.source_address) + self.sock = self._create_connection( + (self.host,self.port), self.timeout, self.source_address) + + try: + self.mss = self.sock.getsockopt( + socket.IPPROTO_TCP, socket.TCP_MAXSEG) + except OSError: + # windows doesn't support TCP_MAXSEG :/ + if self.debuglevel > 0: + print('Unable to determine mss on this platform') if self._tunnel_host: self._tunnel() @@ -807,12 +816,7 @@ ``data`` can be a string object, a bytes object, an array object, a file-like object that supports a .read() method, or an iterable object. """ - - if self.sock is None: - if self.auto_open: - self.connect() - else: - raise NotConnected() + self._try_connect() if self.debuglevel > 0: print("send:", repr(data)) @@ -857,6 +861,13 @@ """ self._buffer.append(s) + def _try_connect(self): + if self.sock is None: + if self.auto_open: + self.connect() + else: + raise NotConnected() + def _send_output(self, message_body=None): """Send the currently buffered request and clear the buffer. @@ -866,15 +877,30 @@ self._buffer.extend((b"", b"")) msg = b"\r\n".join(self._buffer) del self._buffer[:] + + # allows the TCP handshake to occur and have the MSS determined + self._try_connect() + # If msg and message_body are sent in a single send() call, # it will avoid performance problems caused by the interaction # between delayed ack and the Nagle algorithm. However, # there is no performance gain if the message is larger # than MSS (and there is a memory penalty for the message # copy). - if isinstance(message_body, bytes) and len(message_body) < self.mss: + # + # RFC 6691 states that the sender MUST reduce the TCP data length + # to account for any IP or TCP options that it is including in the + # packets that it sends. + # + # This is a best guess attempt at sending the request in a single + # packet, assuming minimal TCP and IP headers. If either options TCP or + # IP options are set (or IPv6 extension headers are used), the message + # may still end up being transmitted over multiple packets. + if isinstance(message_body, bytes) and \ + len(msg) + len(message_body) <= self.mss: msg += message_body message_body = None + self.send(msg) if message_body is not None: # message_body was not a string (i.e. it is a file), and diff -r 031fc0231f3d Lib/test/test_httplib.py --- a/Lib/test/test_httplib.py Thu Jan 15 22:53:21 2015 +0100 +++ b/Lib/test/test_httplib.py Thu Jan 22 09:01:12 2015 -0800 @@ -70,6 +70,12 @@ def close(self): pass + def getsockopt(self, level, name): + if level == socket.IPPROTO_TCP: + if name == socket.TCP_MAXSEG: + return 16332 + raise NotImplementedError + class EPipeSocket(FakeSocket): def __init__(self, text, pipe_trigger): @@ -666,7 +672,7 @@ conn = client.HTTPConnection('example.com') sock = FakeSocket(None) conn.sock = sock - body = b'x' * (conn.mss - 1) + body = b'x' conn.request('POST', '/', body) self.assertEqual(sock.sendall_calls, 1)