diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -774,15 +774,81 @@ - :meth:`~socket.socket.listen()` - :meth:`~socket.socket.makefile()` - :meth:`~socket.socket.recv()`, :meth:`~socket.socket.recv_into()` - (but passing a non-zero ``flags`` argument is not allowed) -- :meth:`~socket.socket.send()`, :meth:`~socket.socket.sendall()` (with - the same limitation) +- :meth:`~socket.socket.send()`, :meth:`~socket.socket.sendall()` - :meth:`~socket.socket.shutdown()` However, since the SSL (and TLS) protocol has its own framing atop of TCP, the SSL sockets abstraction can, in certain respects, diverge from the specification of normal, OS-level sockets. See especially the -:ref:`notes on non-blocking sockets `. +:ref:`notes on non-blocking sockets `. The following +methods behave slightly different than their :class:`~socket.socket` +counterparts: + +.. method:: SSLSocket.recv(bufsize[, flags]) + + Receive data from the socket. The return value is a bytes object + representing the data received. The maximum amount of data to be + received at once is specified by *bufsize*. The *flags* argument is + accepted for compatibility with the regular socket's + :meth:`~socket.socket.recv` method and must be zero if specified. + + If the SSL socket is in non-blocking mode and requires an operation + on the underlying socket that would block, this method will raise + :exc:`SSLWantWriteError` or :exc:`SSLWantReadError` (depending on + what kind of I/O operation on the underlying socket is required). + +.. method:: SSLSocket.recv_into(buffer[, nbytes[, flags]]) + + Receive up to *nbytes* bytes from the socket, storing the data into + a buffer rather than creating a new bytestring. If *nbytes* is not + specified (or 0), receive up to the size available in the given + buffer. Returns the number of bytes received. The *flags* + argument is accepted for compatibility with the regular socket's + :meth:`~socket.socket.recv_into` method and must be zero if + specified. + + If the SSL socket is in non-blocking mode and requires an operation + on the underlying socket that would block, this method will raise + :exc:`SSLWantWriteError` or :exc:`SSLWantReadError` (depending on + what kind of I/O operation on the underlying socket is required). + +.. method:: SSLSocket.send(buf[, flags]) + + Send data to the socket. Returns the number of bytes sent (which + may be less than the length of *buf*). The *flags* argument is + accepted for compatibility with the regular socket's + :meth:`~socket.socket.send` method and must be zero if specified. + + If the SSL socket is in non-blocking mode and requires an operation + on the underlying socket that would block, this method will raise + :exc:`SSLWantWriteError` or :exc:`SSLWantReadError` (depending on + what kind of I/O operation on the underlying socket is required). + + .. versionchanged:: 3.5 + + In earlier Python versions, this method returned zero instead of + raising :exc:`SSLWantWriteError` or :exc:`SSLWantReadError`. + +.. method:: SSLSocket.sendall(buf[, flags]) + + Send data to the socket. Unlike :meth:`send`, this method continues + to send data from *buf* until either all data has been sent or an + error occurs. ``None`` is returned on success. On error, an + exception is raised, and there is no way to determine how much + data, if any, was successfully sent. The *flags* argument is + accepted for compatibility with the regular socket's + :meth:`~socket.socket.sendall` method and must be zero if + specified. + + If the SSL socket is in non-blocking mode and requires an operation + on the underlying socket that would block, this method will raise + :exc:`SSLWantWriteError` or :exc:`SSLWantReadError` (depending on + what kind of I/O operation on the underlying socket is required). + + .. versionchanged:: 3.5 + + In earlier Python versions, this method returned zero instead of + raising :exc:`SSLWantWriteError` or :exc:`SSLWantReadError`. SSL sockets also have the following additional methods and attributes: @@ -1592,8 +1658,13 @@ Notes on non-blocking sockets ----------------------------- -When working with non-blocking sockets, there are several things you need -to be aware of: +SSL sockets behave slightly different than regular sockets in +non-blocking mode. When working with non-blocking sockets, there are +thus several things you need to be aware of: + +- Most :class:`SSLSocket` methods will raise either + :exc:`SSLWantWriteError` or :exc:`SSLWantReadError` instead of + :exc:`BlockingIOError` if an I/O operation would block. - Calling :func:`~select.select` tells you that the OS-level socket can be read from (or written to), but it does not imply that there is sufficient @@ -1619,7 +1690,6 @@ except ssl.SSLWantWriteError: select.select([], [sock], []) - .. _ssl-security: Security considerations diff --git a/Lib/ssl.py b/Lib/ssl.py --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -664,17 +664,7 @@ raise ValueError( "non-zero flags not allowed in calls to send() on %s" % self.__class__) - try: - v = self._sslobj.write(data) - except SSLError as x: - if x.args[0] == SSL_ERROR_WANT_READ: - return 0 - elif x.args[0] == SSL_ERROR_WANT_WRITE: - return 0 - else: - raise - else: - return v + return self._sslobj.write(data) else: return socket.send(self, data, flags) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -2476,6 +2476,35 @@ s.write(b"over\n") s.close() + def test_nonblocking_send(self): + server = ThreadedEchoServer(CERTFILE, + certreqs=ssl.CERT_NONE, + ssl_version=ssl.PROTOCOL_TLSv1, + cacerts=CERTFILE, + chatty=True, + connectionchatty=False) + with server: + s = ssl.wrap_socket(socket.socket(), + server_side=False, + certfile=CERTFILE, + ca_certs=CERTFILE, + cert_reqs=ssl.CERT_NONE, + ssl_version=ssl.PROTOCOL_TLSv1) + s.connect((HOST, server.port)) + s.setblocking(False) + + # If we keep sending data, at some point the buffers + # will be full and the call will block + buf = bytearray(8192) + def fill_buffer(): + while True: + s.send(buf) + self.assertRaises(ssl.SSLWantWriteError, fill_buffer) + + # Now read all the output and discard it + s.setblocking(True) + s.close() + def test_handshake_timeout(self): # Issue #5103: SSL handshake must respect the socket timeout server = socket.socket(socket.AF_INET)