mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	fix issue #17552: add socket.sendfile() method allowing to send a file over a socket by using high-performance os.sendfile() on UNIX. Patch by Giampaolo Rodola'·
This commit is contained in:
		
							parent
							
								
									b398d33c65
								
							
						
					
					
						commit
						915d14190e
					
				
					 9 changed files with 483 additions and 2 deletions
				
			
		|  | @ -1092,6 +1092,10 @@ or `the MSDN <http://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx>`_ on Window | |||
| 
 | ||||
|    Availability: Unix. | ||||
| 
 | ||||
|    .. note:: | ||||
| 
 | ||||
|       For a higher-level version of this see :mod:`socket.socket.sendfile`. | ||||
| 
 | ||||
|    .. versionadded:: 3.3 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1148,6 +1148,21 @@ to sockets. | |||
| 
 | ||||
|    .. versionadded:: 3.3 | ||||
| 
 | ||||
| .. method:: socket.sendfile(file, offset=0, count=None) | ||||
| 
 | ||||
|    Send a file until EOF is reached by using high-performance | ||||
|    :mod:`os.sendfile` and return the total number of bytes which were sent. | ||||
|    *file* must be a regular file object opened in binary mode. If | ||||
|    :mod:`os.sendfile` is not available (e.g. Windows) or *file* is not a | ||||
|    regular file :meth:`send` will be used instead. *offset* tells from where to | ||||
|    start reading the file. If specified, *count* is the total number of bytes | ||||
|    to transmit as opposed to sending the file until EOF is reached. File | ||||
|    position is updated on return or also in case of error in which case | ||||
|    :meth:`file.tell() <io.IOBase.tell>` can be used to figure out the number of | ||||
|    bytes which were sent. The socket must be of :const:`SOCK_STREAM` type. Non- | ||||
|    blocking sockets are not supported. | ||||
| 
 | ||||
|    .. versionadded:: 3.5 | ||||
| 
 | ||||
| .. method:: socket.set_inheritable(inheritable) | ||||
| 
 | ||||
|  |  | |||
|  | @ -789,6 +789,9 @@ SSL sockets provide the following methods of :ref:`socket-objects`: | |||
|   (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.sendfile()` (but :mod:`os.sendfile` will be used | ||||
|   for plain-text sockets only, else :meth:`~socket.socket.send()` will be used) | ||||
|    .. versionadded:: 3.5 | ||||
| - :meth:`~socket.socket.shutdown()` | ||||
| 
 | ||||
| However, since the SSL (and TLS) protocol has its own framing atop | ||||
|  |  | |||
|  | @ -181,9 +181,18 @@ signal | |||
| 
 | ||||
| * Different constants of :mod:`signal` module are now enumeration values using | ||||
|   the :mod:`enum` module. This allows meaningful names to be printed during | ||||
|   debugging, instead of integer “magic numbers”. (contribute by Giampaolo | ||||
|   debugging, instead of integer “magic numbers”. (contributed by Giampaolo | ||||
|   Rodola' in :issue:`21076`) | ||||
| 
 | ||||
| socket | ||||
| ------ | ||||
| 
 | ||||
| * New :meth:`socket.socket.sendfile` method allows to send a file over a socket | ||||
|   by using high-performance :func:`os.sendfile` function on UNIX resulting in | ||||
|   uploads being from 2x to 3x faster than when using plain | ||||
|   :meth:`socket.socket.send`. | ||||
|   (contributed by Giampaolo Rodola' in :issue:`17552`) | ||||
| 
 | ||||
| xmlrpc | ||||
| ------ | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										148
									
								
								Lib/socket.py
									
										
									
									
									
								
							
							
						
						
									
										148
									
								
								Lib/socket.py
									
										
									
									
									
								
							|  | @ -47,7 +47,7 @@ | |||
| import _socket | ||||
| from _socket import * | ||||
| 
 | ||||
| import os, sys, io | ||||
| import os, sys, io, selectors | ||||
| from enum import IntEnum | ||||
| 
 | ||||
| try: | ||||
|  | @ -109,6 +109,9 @@ def _intenum_converter(value, enum_klass): | |||
|     __all__.append("errorTab") | ||||
| 
 | ||||
| 
 | ||||
| class _GiveupOnSendfile(Exception): pass | ||||
| 
 | ||||
| 
 | ||||
| class socket(_socket.socket): | ||||
| 
 | ||||
|     """A subclass of _socket.socket adding the makefile() method.""" | ||||
|  | @ -233,6 +236,149 @@ def makefile(self, mode="r", buffering=None, *, | |||
|         text.mode = mode | ||||
|         return text | ||||
| 
 | ||||
|     if hasattr(os, 'sendfile'): | ||||
| 
 | ||||
|         def _sendfile_use_sendfile(self, file, offset=0, count=None): | ||||
|             self._check_sendfile_params(file, offset, count) | ||||
|             sockno = self.fileno() | ||||
|             try: | ||||
|                 fileno = file.fileno() | ||||
|             except (AttributeError, io.UnsupportedOperation) as err: | ||||
|                 raise _GiveupOnSendfile(err)  # not a regular file | ||||
|             try: | ||||
|                 fsize = os.fstat(fileno).st_size | ||||
|             except OSError: | ||||
|                 raise _GiveupOnSendfile(err)  # not a regular file | ||||
|             if not fsize: | ||||
|                 return 0  # empty file | ||||
|             blocksize = fsize if not count else count | ||||
| 
 | ||||
|             timeout = self.gettimeout() | ||||
|             if timeout == 0: | ||||
|                 raise ValueError("non-blocking sockets are not supported") | ||||
|             # poll/select have the advantage of not requiring any | ||||
|             # extra file descriptor, contrarily to epoll/kqueue | ||||
|             # (also, they require a single syscall). | ||||
|             if hasattr(selectors, 'PollSelector'): | ||||
|                 selector = selectors.PollSelector() | ||||
|             else: | ||||
|                 selector = selectors.SelectSelector() | ||||
|             selector.register(sockno, selectors.EVENT_WRITE) | ||||
| 
 | ||||
|             total_sent = 0 | ||||
|             # localize variable access to minimize overhead | ||||
|             selector_select = selector.select | ||||
|             os_sendfile = os.sendfile | ||||
|             try: | ||||
|                 while True: | ||||
|                     if timeout and not selector_select(timeout): | ||||
|                         raise _socket.timeout('timed out') | ||||
|                     if count: | ||||
|                         blocksize = count - total_sent | ||||
|                         if blocksize <= 0: | ||||
|                             break | ||||
|                     try: | ||||
|                         sent = os_sendfile(sockno, fileno, offset, blocksize) | ||||
|                     except BlockingIOError: | ||||
|                         if not timeout: | ||||
|                             # Block until the socket is ready to send some | ||||
|                             # data; avoids hogging CPU resources. | ||||
|                             selector_select() | ||||
|                         continue | ||||
|                     except OSError as err: | ||||
|                         if total_sent == 0: | ||||
|                             # We can get here for different reasons, the main | ||||
|                             # one being 'file' is not a regular mmap(2)-like | ||||
|                             # file, in which case we'll fall back on using | ||||
|                             # plain send(). | ||||
|                             raise _GiveupOnSendfile(err) | ||||
|                         raise err from None | ||||
|                     else: | ||||
|                         if sent == 0: | ||||
|                             break  # EOF | ||||
|                         offset += sent | ||||
|                         total_sent += sent | ||||
|                 return total_sent | ||||
|             finally: | ||||
|                 if total_sent > 0 and hasattr(file, 'seek'): | ||||
|                     file.seek(offset) | ||||
|     else: | ||||
|         def _sendfile_use_sendfile(self, file, offset=0, count=None): | ||||
|             raise _GiveupOnSendfile( | ||||
|                 "os.sendfile() not available on this platform") | ||||
| 
 | ||||
|     def _sendfile_use_send(self, file, offset=0, count=None): | ||||
|         self._check_sendfile_params(file, offset, count) | ||||
|         if self.gettimeout() == 0: | ||||
|             raise ValueError("non-blocking sockets are not supported") | ||||
|         if offset: | ||||
|             file.seek(offset) | ||||
|         blocksize = min(count, 8192) if count else 8192 | ||||
|         total_sent = 0 | ||||
|         # localize variable access to minimize overhead | ||||
|         file_read = file.read | ||||
|         sock_send = self.send | ||||
|         try: | ||||
|             while True: | ||||
|                 if count: | ||||
|                     blocksize = min(count - total_sent, blocksize) | ||||
|                     if blocksize <= 0: | ||||
|                         break | ||||
|                 data = memoryview(file_read(blocksize)) | ||||
|                 if not data: | ||||
|                     break  # EOF | ||||
|                 while True: | ||||
|                     try: | ||||
|                         sent = sock_send(data) | ||||
|                     except BlockingIOError: | ||||
|                         continue | ||||
|                     else: | ||||
|                         total_sent += sent | ||||
|                         if sent < len(data): | ||||
|                             data = data[sent:] | ||||
|                         else: | ||||
|                             break | ||||
|             return total_sent | ||||
|         finally: | ||||
|             if total_sent > 0 and hasattr(file, 'seek'): | ||||
|                 file.seek(offset + total_sent) | ||||
| 
 | ||||
|     def _check_sendfile_params(self, file, offset, count): | ||||
|         if 'b' not in getattr(file, 'mode', 'b'): | ||||
|             raise ValueError("file should be opened in binary mode") | ||||
|         if not self.type & SOCK_STREAM: | ||||
|             raise ValueError("only SOCK_STREAM type sockets are supported") | ||||
|         if count is not None: | ||||
|             if not isinstance(count, int): | ||||
|                 raise TypeError( | ||||
|                     "count must be a positive integer (got {!r})".format(count)) | ||||
|             if count <= 0: | ||||
|                 raise ValueError( | ||||
|                     "count must be a positive integer (got {!r})".format(count)) | ||||
| 
 | ||||
|     def sendfile(self, file, offset=0, count=None): | ||||
|         """sendfile(file[, offset[, count]]) -> sent | ||||
| 
 | ||||
|         Send a file until EOF is reached by using high-performance | ||||
|         os.sendfile() and return the total number of bytes which | ||||
|         were sent. | ||||
|         *file* must be a regular file object opened in binary mode. | ||||
|         If os.sendfile() is not available (e.g. Windows) or file is | ||||
|         not a regular file socket.send() will be used instead. | ||||
|         *offset* tells from where to start reading the file. | ||||
|         If specified, *count* is the total number of bytes to transmit | ||||
|         as opposed to sending the file until EOF is reached. | ||||
|         File position is updated on return or also in case of error in | ||||
|         which case file.tell() can be used to figure out the number of | ||||
|         bytes which were sent. | ||||
|         The socket must be of SOCK_STREAM type. | ||||
|         Non-blocking sockets are not supported. | ||||
|         """ | ||||
|         try: | ||||
|             return self._sendfile_use_sendfile(file, offset, count) | ||||
|         except _GiveupOnSendfile: | ||||
|             return self._sendfile_use_send(file, offset, count) | ||||
| 
 | ||||
|     def _decref_socketios(self): | ||||
|         if self._io_refs > 0: | ||||
|             self._io_refs -= 1 | ||||
|  |  | |||
							
								
								
									
										10
									
								
								Lib/ssl.py
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								Lib/ssl.py
									
										
									
									
									
								
							|  | @ -700,6 +700,16 @@ def sendall(self, data, flags=0): | |||
|         else: | ||||
|             return socket.sendall(self, data, flags) | ||||
| 
 | ||||
|     def sendfile(self, file, offset=0, count=None): | ||||
|         """Send a file, possibly by using os.sendfile() if this is a | ||||
|         clear-text socket.  Return the total number of bytes sent. | ||||
|         """ | ||||
|         if self._sslobj is None: | ||||
|             # os.sendfile() works with plain sockets only | ||||
|             return super().sendfile(file, offset, count) | ||||
|         else: | ||||
|             return self._sendfile_use_send(file, offset, count) | ||||
| 
 | ||||
|     def recv(self, buflen=1024, flags=0): | ||||
|         self._checkClosed() | ||||
|         if self._sslobj: | ||||
|  |  | |||
|  | @ -19,6 +19,8 @@ | |||
| import math | ||||
| import pickle | ||||
| import struct | ||||
| import random | ||||
| import string | ||||
| try: | ||||
|     import multiprocessing | ||||
| except ImportError: | ||||
|  | @ -5077,6 +5079,275 @@ def testTypes(self): | |||
|                     source.close() | ||||
| 
 | ||||
| 
 | ||||
| @unittest.skipUnless(thread, 'Threading required for this test.') | ||||
| class SendfileUsingSendTest(ThreadedTCPSocketTest): | ||||
|     """ | ||||
|     Test the send() implementation of socket.sendfile(). | ||||
|     """ | ||||
| 
 | ||||
|     FILESIZE = (10 * 1024 * 1024)  # 10MB | ||||
|     BUFSIZE = 8192 | ||||
|     FILEDATA = b"" | ||||
|     TIMEOUT = 2 | ||||
| 
 | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         def chunks(total, step): | ||||
|             assert total >= step | ||||
|             while total > step: | ||||
|                 yield step | ||||
|                 total -= step | ||||
|             if total: | ||||
|                 yield total | ||||
| 
 | ||||
|         chunk = b"".join([random.choice(string.ascii_letters).encode() | ||||
|                           for i in range(cls.BUFSIZE)]) | ||||
|         with open(support.TESTFN, 'wb') as f: | ||||
|             for csize in chunks(cls.FILESIZE, cls.BUFSIZE): | ||||
|                 f.write(chunk) | ||||
|         with open(support.TESTFN, 'rb') as f: | ||||
|             cls.FILEDATA = f.read() | ||||
|             assert len(cls.FILEDATA) == cls.FILESIZE | ||||
| 
 | ||||
|     @classmethod | ||||
|     def tearDownClass(cls): | ||||
|         support.unlink(support.TESTFN) | ||||
| 
 | ||||
|     def accept_conn(self): | ||||
|         self.serv.settimeout(self.TIMEOUT) | ||||
|         conn, addr = self.serv.accept() | ||||
|         conn.settimeout(self.TIMEOUT) | ||||
|         self.addCleanup(conn.close) | ||||
|         return conn | ||||
| 
 | ||||
|     def recv_data(self, conn): | ||||
|         received = [] | ||||
|         while True: | ||||
|             chunk = conn.recv(self.BUFSIZE) | ||||
|             if not chunk: | ||||
|                 break | ||||
|             received.append(chunk) | ||||
|         return b''.join(received) | ||||
| 
 | ||||
|     def meth_from_sock(self, sock): | ||||
|         # Depending on the mixin class being run return either send() | ||||
|         # or sendfile() method implementation. | ||||
|         return getattr(sock, "_sendfile_use_send") | ||||
| 
 | ||||
|     # regular file | ||||
| 
 | ||||
|     def _testRegularFile(self): | ||||
|         address = self.serv.getsockname() | ||||
|         file = open(support.TESTFN, 'rb') | ||||
|         with socket.create_connection(address) as sock, file as file: | ||||
|             meth = self.meth_from_sock(sock) | ||||
|             sent = meth(file) | ||||
|             self.assertEqual(sent, self.FILESIZE) | ||||
|             self.assertEqual(file.tell(), self.FILESIZE) | ||||
| 
 | ||||
|     def testRegularFile(self): | ||||
|         conn = self.accept_conn() | ||||
|         data = self.recv_data(conn) | ||||
|         self.assertEqual(len(data), self.FILESIZE) | ||||
|         self.assertEqual(data, self.FILEDATA) | ||||
| 
 | ||||
|     # non regular file | ||||
| 
 | ||||
|     def _testNonRegularFile(self): | ||||
|         address = self.serv.getsockname() | ||||
|         file = io.BytesIO(self.FILEDATA) | ||||
|         with socket.create_connection(address) as sock, file as file: | ||||
|             sent = sock.sendfile(file) | ||||
|             self.assertEqual(sent, self.FILESIZE) | ||||
|             self.assertEqual(file.tell(), self.FILESIZE) | ||||
|             self.assertRaises(socket._GiveupOnSendfile, | ||||
|                               sock._sendfile_use_sendfile, file) | ||||
| 
 | ||||
|     def testNonRegularFile(self): | ||||
|         conn = self.accept_conn() | ||||
|         data = self.recv_data(conn) | ||||
|         self.assertEqual(len(data), self.FILESIZE) | ||||
|         self.assertEqual(data, self.FILEDATA) | ||||
| 
 | ||||
|     # empty file | ||||
| 
 | ||||
|     def _testEmptyFileSend(self): | ||||
|         address = self.serv.getsockname() | ||||
|         filename = support.TESTFN + "2" | ||||
|         with open(filename, 'wb'): | ||||
|             self.addCleanup(support.unlink, filename) | ||||
|         file = open(filename, 'rb') | ||||
|         with socket.create_connection(address) as sock, file as file: | ||||
|             meth = self.meth_from_sock(sock) | ||||
|             sent = meth(file) | ||||
|             self.assertEqual(sent, 0) | ||||
|             self.assertEqual(file.tell(), 0) | ||||
| 
 | ||||
|     def testEmptyFileSend(self): | ||||
|         conn = self.accept_conn() | ||||
|         data = self.recv_data(conn) | ||||
|         self.assertEqual(data, b"") | ||||
| 
 | ||||
|     # offset | ||||
| 
 | ||||
|     def _testOffset(self): | ||||
|         address = self.serv.getsockname() | ||||
|         file = open(support.TESTFN, 'rb') | ||||
|         with socket.create_connection(address) as sock, file as file: | ||||
|             meth = self.meth_from_sock(sock) | ||||
|             sent = meth(file, offset=5000) | ||||
|             self.assertEqual(sent, self.FILESIZE - 5000) | ||||
|             self.assertEqual(file.tell(), self.FILESIZE) | ||||
| 
 | ||||
|     def testOffset(self): | ||||
|         conn = self.accept_conn() | ||||
|         data = self.recv_data(conn) | ||||
|         self.assertEqual(len(data), self.FILESIZE - 5000) | ||||
|         self.assertEqual(data, self.FILEDATA[5000:]) | ||||
| 
 | ||||
|     # count | ||||
| 
 | ||||
|     def _testCount(self): | ||||
|         address = self.serv.getsockname() | ||||
|         file = open(support.TESTFN, 'rb') | ||||
|         with socket.create_connection(address, timeout=2) as sock, file as file: | ||||
|             count = 5000007 | ||||
|             meth = self.meth_from_sock(sock) | ||||
|             sent = meth(file, count=count) | ||||
|             self.assertEqual(sent, count) | ||||
|             self.assertEqual(file.tell(), count) | ||||
| 
 | ||||
|     def testCount(self): | ||||
|         count = 5000007 | ||||
|         conn = self.accept_conn() | ||||
|         data = self.recv_data(conn) | ||||
|         self.assertEqual(len(data), count) | ||||
|         self.assertEqual(data, self.FILEDATA[:count]) | ||||
| 
 | ||||
|     # count small | ||||
| 
 | ||||
|     def _testCountSmall(self): | ||||
|         address = self.serv.getsockname() | ||||
|         file = open(support.TESTFN, 'rb') | ||||
|         with socket.create_connection(address, timeout=2) as sock, file as file: | ||||
|             count = 1 | ||||
|             meth = self.meth_from_sock(sock) | ||||
|             sent = meth(file, count=count) | ||||
|             self.assertEqual(sent, count) | ||||
|             self.assertEqual(file.tell(), count) | ||||
| 
 | ||||
|     def testCountSmall(self): | ||||
|         count = 1 | ||||
|         conn = self.accept_conn() | ||||
|         data = self.recv_data(conn) | ||||
|         self.assertEqual(len(data), count) | ||||
|         self.assertEqual(data, self.FILEDATA[:count]) | ||||
| 
 | ||||
|     # count + offset | ||||
| 
 | ||||
|     def _testCountWithOffset(self): | ||||
|         address = self.serv.getsockname() | ||||
|         file = open(support.TESTFN, 'rb') | ||||
|         with socket.create_connection(address, timeout=2) as sock, file as file: | ||||
|             count = 100007 | ||||
|             meth = self.meth_from_sock(sock) | ||||
|             sent = meth(file, offset=2007, count=count) | ||||
|             self.assertEqual(sent, count) | ||||
|             self.assertEqual(file.tell(), count + 2007) | ||||
| 
 | ||||
|     def testCountWithOffset(self): | ||||
|         count = 100007 | ||||
|         conn = self.accept_conn() | ||||
|         data = self.recv_data(conn) | ||||
|         self.assertEqual(len(data), count) | ||||
|         self.assertEqual(data, self.FILEDATA[2007:count+2007]) | ||||
| 
 | ||||
|     # non blocking sockets are not supposed to work | ||||
| 
 | ||||
|     def _testNonBlocking(self): | ||||
|         address = self.serv.getsockname() | ||||
|         file = open(support.TESTFN, 'rb') | ||||
|         with socket.create_connection(address) as sock, file as file: | ||||
|             sock.setblocking(False) | ||||
|             meth = self.meth_from_sock(sock) | ||||
|             self.assertRaises(ValueError, meth, file) | ||||
|             self.assertRaises(ValueError, sock.sendfile, file) | ||||
| 
 | ||||
|     def testNonBlocking(self): | ||||
|         conn = self.accept_conn() | ||||
|         if conn.recv(8192): | ||||
|             self.fail('was not supposed to receive any data') | ||||
| 
 | ||||
|     # timeout (non-triggered) | ||||
| 
 | ||||
|     def _testWithTimeout(self): | ||||
|         address = self.serv.getsockname() | ||||
|         file = open(support.TESTFN, 'rb') | ||||
|         with socket.create_connection(address, timeout=2) as sock, file as file: | ||||
|             meth = self.meth_from_sock(sock) | ||||
|             sent = meth(file) | ||||
|             self.assertEqual(sent, self.FILESIZE) | ||||
| 
 | ||||
|     def testWithTimeout(self): | ||||
|         conn = self.accept_conn() | ||||
|         data = self.recv_data(conn) | ||||
|         self.assertEqual(len(data), self.FILESIZE) | ||||
|         self.assertEqual(data, self.FILEDATA) | ||||
| 
 | ||||
|     # timeout (triggered) | ||||
| 
 | ||||
|     def _testWithTimeoutTriggeredSend(self): | ||||
|         address = self.serv.getsockname() | ||||
|         file = open(support.TESTFN, 'rb') | ||||
|         with socket.create_connection(address, timeout=0.01) as sock, \ | ||||
|                 file as file: | ||||
|             meth = self.meth_from_sock(sock) | ||||
|             self.assertRaises(socket.timeout, meth, file) | ||||
| 
 | ||||
|     def testWithTimeoutTriggeredSend(self): | ||||
|         conn = self.accept_conn() | ||||
|         conn.recv(88192) | ||||
| 
 | ||||
|     # errors | ||||
| 
 | ||||
|     def _test_errors(self): | ||||
|         pass | ||||
| 
 | ||||
|     def test_errors(self): | ||||
|         with open(support.TESTFN, 'rb') as file: | ||||
|             with socket.socket(type=socket.SOCK_DGRAM) as s: | ||||
|                 meth = self.meth_from_sock(s) | ||||
|                 self.assertRaisesRegex( | ||||
|                     ValueError, "SOCK_STREAM", meth, file) | ||||
|         with open(support.TESTFN, 'rt') as file: | ||||
|             with socket.socket() as s: | ||||
|                 meth = self.meth_from_sock(s) | ||||
|                 self.assertRaisesRegex( | ||||
|                     ValueError, "binary mode", meth, file) | ||||
|         with open(support.TESTFN, 'rb') as file: | ||||
|             with socket.socket() as s: | ||||
|                 meth = self.meth_from_sock(s) | ||||
|                 self.assertRaisesRegex(TypeError, "positive integer", | ||||
|                                        meth, file, count='2') | ||||
|                 self.assertRaisesRegex(TypeError, "positive integer", | ||||
|                                        meth, file, count=0.1) | ||||
|                 self.assertRaisesRegex(ValueError, "positive integer", | ||||
|                                        meth, file, count=0) | ||||
|                 self.assertRaisesRegex(ValueError, "positive integer", | ||||
|                                        meth, file, count=-1) | ||||
| 
 | ||||
| 
 | ||||
| @unittest.skipUnless(thread, 'Threading required for this test.') | ||||
| @unittest.skipUnless(hasattr(os, "sendfile"), | ||||
|                      'os.sendfile() required for this test.') | ||||
| class SendfileUsingSendfileTest(SendfileUsingSendTest): | ||||
|     """ | ||||
|     Test the sendfile() implementation of socket.sendfile(). | ||||
|     """ | ||||
|     def meth_from_sock(self, sock): | ||||
|         return getattr(sock, "_sendfile_use_sendfile") | ||||
| 
 | ||||
| 
 | ||||
| def test_main(): | ||||
|     tests = [GeneralModuleTests, BasicTCPTest, TCPCloserTest, TCPTimeoutTest, | ||||
|              TestExceptions, BufferIOTest, BasicTCPTest2, BasicUDPTest, UDPTimeoutTest ] | ||||
|  | @ -5129,6 +5400,8 @@ def test_main(): | |||
|         InterruptedRecvTimeoutTest, | ||||
|         InterruptedSendTimeoutTest, | ||||
|         TestSocketSharing, | ||||
|         SendfileUsingSendTest, | ||||
|         SendfileUsingSendfileTest, | ||||
|     ]) | ||||
| 
 | ||||
|     thread_info = support.threading_setup() | ||||
|  |  | |||
|  | @ -2957,6 +2957,23 @@ def test_read_write_after_close_raises_valuerror(self): | |||
|                 self.assertRaises(ValueError, s.read, 1024) | ||||
|                 self.assertRaises(ValueError, s.write, b'hello') | ||||
| 
 | ||||
|         def test_sendfile(self): | ||||
|             TEST_DATA = b"x" * 512 | ||||
|             with open(support.TESTFN, 'wb') as f: | ||||
|                 f.write(TEST_DATA) | ||||
|             self.addCleanup(support.unlink, support.TESTFN) | ||||
|             context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) | ||||
|             context.verify_mode = ssl.CERT_REQUIRED | ||||
|             context.load_verify_locations(CERTFILE) | ||||
|             context.load_cert_chain(CERTFILE) | ||||
|             server = ThreadedEchoServer(context=context, chatty=False) | ||||
|             with server: | ||||
|                 with context.wrap_socket(socket.socket()) as s: | ||||
|                     s.connect((HOST, server.port)) | ||||
|                     with open(support.TESTFN, 'rb') as file: | ||||
|                         s.sendfile(file) | ||||
|                         self.assertEqual(s.recv(1024), TEST_DATA) | ||||
| 
 | ||||
| 
 | ||||
| def test_main(verbose=False): | ||||
|     if support.verbose: | ||||
|  |  | |||
|  | @ -92,6 +92,10 @@ Core and Builtins | |||
| Library | ||||
| ------- | ||||
| 
 | ||||
| - Issue 17552: new socket.sendfile() method allowing to send a file over a | ||||
|   socket by using high-performance os.sendfile() on UNIX. | ||||
|   Patch by Giampaolo Rodola'. | ||||
| 
 | ||||
| - Issue #18039: dbm.dump.open() now always creates a new database when the | ||||
|   flag has the value 'n'.  Patch by Claudiu Popa. | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Giampaolo Rodola'
						Giampaolo Rodola'