From ded8bb4cc68cc225151f1339412148b121481146 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Sun, 15 Dec 2024 14:08:47 -0800 Subject: [PATCH 1/6] Clarify and clean up the separation between BaseServer and TCPServer --- Doc/library/socketserver.rst | 99 ++++++++++--------- Lib/socketserver.py | 44 ++++++--- ...-12-15-14-08-40.gh-issue-127209.17oPD0.rst | 3 + 3 files changed, 84 insertions(+), 62 deletions(-) create mode 100644 Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst diff --git a/Doc/library/socketserver.rst b/Doc/library/socketserver.rst index 69f06e6cf4d923..54dce7a6dce575 100644 --- a/Doc/library/socketserver.rst +++ b/Doc/library/socketserver.rst @@ -24,6 +24,55 @@ There are four basic concrete server classes: :meth:`~BaseServer.server_activate`. The other parameters are passed to the :class:`BaseServer` base class. + In addition to the methods and attributes inherited from :class:`BaseServer`, + :class:`TCPServer` provides: + + + .. method:: fileno() + + Return an integer file descriptor for the socket on which the server is + listening. This function is most commonly passed to :mod:`selectors`, to + allow monitoring multiple servers in the same process. + + + .. method:: server_bind() + + Called by the server's constructor to bind the socket to the desired address. + May be overridden. + + + .. attribute:: address_family + + The family of protocols to which the server's socket belongs. + Common examples are :const:`socket.AF_INET` and :const:`socket.AF_UNIX`. + + + .. attribute:: socket + + The socket object on which the server will listen for incoming requests. + + + .. attribute:: allow_reuse_address + + Whether the server will allow the reuse of an address. This defaults to + :const:`False`, and can be set in subclasses to change the policy. + + + .. attribute:: request_queue_size + + The size of the request queue. If it takes a long time to process a single + request, any requests that arrive while the server is busy are placed into a + queue, up to :attr:`request_queue_size` requests. Once the queue is full, + further requests from clients will get a "Connection denied" error. The default + value is usually 5, but this can be overridden by subclasses. + + + .. attribute:: socket_type + + The type of socket used by the server; :const:`socket.SOCK_STREAM` and + :const:`socket.SOCK_DGRAM` are two common values. + + .. class:: UDPServer(server_address, RequestHandlerClass, bind_and_activate=True) @@ -211,13 +260,6 @@ Server Objects :attr:`server_address` and :attr:`RequestHandlerClass` attributes. - .. method:: fileno() - - Return an integer file descriptor for the socket on which the server is - listening. This function is most commonly passed to :mod:`selectors`, to - allow monitoring multiple servers in the same process. - - .. method:: handle_request() Process a single request. This function calls the following methods in @@ -264,12 +306,6 @@ Server Objects Clean up the server. May be overridden. - .. attribute:: address_family - - The family of protocols to which the server's socket belongs. - Common examples are :const:`socket.AF_INET` and :const:`socket.AF_UNIX`. - - .. attribute:: RequestHandlerClass The user-provided request handler class; an instance of this class is created @@ -285,36 +321,10 @@ Server Objects the address, and an integer port number: ``('127.0.0.1', 80)``, for example. - .. attribute:: socket - - The socket object on which the server will listen for incoming requests. - - The server classes support the following class variables: .. XXX should class variables be covered before instance variables, or vice versa? - .. attribute:: allow_reuse_address - - Whether the server will allow the reuse of an address. This defaults to - :const:`False`, and can be set in subclasses to change the policy. - - - .. attribute:: request_queue_size - - The size of the request queue. If it takes a long time to process a single - request, any requests that arrive while the server is busy are placed into a - queue, up to :attr:`request_queue_size` requests. Once the queue is full, - further requests from clients will get a "Connection denied" error. The default - value is usually 5, but this can be overridden by subclasses. - - - .. attribute:: socket_type - - The type of socket used by the server; :const:`socket.SOCK_STREAM` and - :const:`socket.SOCK_DGRAM` are two common values. - - .. attribute:: timeout Timeout duration, measured in seconds, or :const:`None` if no timeout is @@ -341,6 +351,10 @@ Server Objects socket object to be used to communicate with the client, and the client's address. + An implementation of :meth:`get_request` is provided by :class:`TCPServer`. + Other classes which inherit from :class:`BaseServer` directly must provide + their own implementation. + .. method:: handle_error(request, client_address) @@ -382,12 +396,6 @@ Server Objects on the server's socket. May be overridden. - .. method:: server_bind() - - Called by the server's constructor to bind the socket to the desired address. - May be overridden. - - .. method:: verify_request(request, client_address) Must return a Boolean value; if the value is :const:`True`, the request will @@ -697,4 +705,3 @@ The output of the example should look something like this: The :class:`ForkingMixIn` class is used in the same way, except that the server will spawn a new process for each request. Available only on POSIX platforms that support :func:`~os.fork`. - diff --git a/Lib/socketserver.py b/Lib/socketserver.py index cd028ef1c63b85..eb56cb6da2d07d 100644 --- a/Lib/socketserver.py +++ b/Lib/socketserver.py @@ -161,14 +161,15 @@ class BaseServer: - __init__(server_address, RequestHandlerClass) - serve_forever(poll_interval=0.5) - shutdown() - - handle_request() # if you do not use serve_forever() - - fileno() -> int # for selector + - handle_request() # if you don't use serve_forever() + + Methods that must be overridden: + + - get_request() -> request, client_address Methods that may be overridden: - - server_bind() - server_activate() - - get_request() -> request, client_address - handle_timeout() - verify_request(request, client_address) - server_close() @@ -186,15 +187,11 @@ class BaseServer: instances: - timeout - - address_family - - socket_type - - allow_reuse_address - - allow_reuse_port Instance variables: + - server_address - RequestHandlerClass - - socket """ @@ -273,18 +270,16 @@ def service_actions(self): # - finish_request() instantiates the request handler class; this # constructor will handle the request all by itself + def _get_timeout(self): + """Hook so child classes can support other sources of timeout.""" + return self.timeout + def handle_request(self): """Handle one request, possibly blocking. Respects self.timeout. """ - # Support people who used socket.settimeout() to escape - # handle_request before self.timeout was available. - timeout = self.socket.gettimeout() - if timeout is None: - timeout = self.timeout - elif self.timeout is not None: - timeout = min(timeout, self.timeout) + timeout = self._get_timeout() if timeout is not None: deadline = time() + timeout @@ -325,6 +320,13 @@ def _handle_request_noblock(self): else: self.shutdown_request(request) + def get_request(self): + """Get the request and client address from the socket. + + Must be overridden by subclasses. + """ + raise NotImplementedError + def handle_timeout(self): """Called if no new request arrives within self.timeout. @@ -489,6 +491,16 @@ def server_close(self): """ self.socket.close() + def _get_timeout(self): + # Support people who used socket.settimeout() to escape + # handle_request before self.timeout was available. + timeout = self.socket.gettimeout() + if timeout is None: + timeout = self.timeout + elif self.timeout is not None: + timeout = min(timeout, self.timeout) + return timeout + def fileno(self): """Return socket file number. diff --git a/Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst b/Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst new file mode 100644 index 00000000000000..6d90258d3f63af --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst @@ -0,0 +1,3 @@ +Updated documentation for :mod:`socketserver` to clarify the differences +between :class:`socketserver.BaseServer` and +:class:`socketserver.TCPServer`. From 0f7dd2c5a3ee99b875ea57483b390436f66d48e1 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Sun, 15 Dec 2024 14:24:38 -0800 Subject: [PATCH 2/6] documentation fix --- Doc/library/socketserver.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/socketserver.rst b/Doc/library/socketserver.rst index 54dce7a6dce575..1c670995e194c1 100644 --- a/Doc/library/socketserver.rst +++ b/Doc/library/socketserver.rst @@ -20,7 +20,7 @@ There are four basic concrete server classes: This uses the internet TCP protocol, which provides for continuous streams of data between the client and server. If *bind_and_activate* is true, the constructor automatically attempts to - invoke :meth:`~BaseServer.server_bind` and + invoke :meth:`~TCPServer.server_bind` and :meth:`~BaseServer.server_activate`. The other parameters are passed to the :class:`BaseServer` base class. From 6c07b1ee50835eaa511026a06dfd10fb3b38dc40 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Mon, 16 Dec 2024 11:19:53 -0800 Subject: [PATCH 3/6] don't add _get_timeout; update NEWS entry --- Lib/socketserver.py | 24 +++++++------------ ...-12-15-14-08-40.gh-issue-127209.17oPD0.rst | 6 ++++- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Lib/socketserver.py b/Lib/socketserver.py index eb56cb6da2d07d..b369c9cecb3e08 100644 --- a/Lib/socketserver.py +++ b/Lib/socketserver.py @@ -270,16 +270,20 @@ def service_actions(self): # - finish_request() instantiates the request handler class; this # constructor will handle the request all by itself - def _get_timeout(self): - """Hook so child classes can support other sources of timeout.""" - return self.timeout - def handle_request(self): """Handle one request, possibly blocking. Respects self.timeout. """ - timeout = self._get_timeout() + # Support people who used socket.settimeout() to escape + # handle_request before self.timeout was available. + timeout = None + if hasattr(self, "socket"): + timeout = self.socket.gettimeout() + if timeout is None: + timeout = self.timeout + elif self.timeout is not None: + timeout = min(timeout, self.timeout) if timeout is not None: deadline = time() + timeout @@ -491,16 +495,6 @@ def server_close(self): """ self.socket.close() - def _get_timeout(self): - # Support people who used socket.settimeout() to escape - # handle_request before self.timeout was available. - timeout = self.socket.gettimeout() - if timeout is None: - timeout = self.timeout - elif self.timeout is not None: - timeout = min(timeout, self.timeout) - return timeout - def fileno(self): """Return socket file number. diff --git a/Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst b/Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst index 6d90258d3f63af..fb0d554e733fcf 100644 --- a/Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst +++ b/Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst @@ -1,3 +1,7 @@ Updated documentation for :mod:`socketserver` to clarify the differences between :class:`socketserver.BaseServer` and -:class:`socketserver.TCPServer`. +:class:`socketserver.TCPServer`. Additionally, :class:`socketserver.BaseServer` +no longer assumes that all subclasses will add a ``socket`` attribute, and +:method:`socketserver.BaseServer.get_request` now raises ``NotImplementedError``. +It was previously absent and attempting to use it would generate an +``AttributeError``. From 9634c76e3e8fb8090be81df3dacf6dc9727d191f Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Mon, 16 Dec 2024 11:29:26 -0800 Subject: [PATCH 4/6] move and reword NEWS entry --- .../2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst | 7 ------- .../Library/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst create mode 100644 Misc/NEWS.d/next/Library/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst diff --git a/Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst b/Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst deleted file mode 100644 index fb0d554e733fcf..00000000000000 --- a/Misc/NEWS.d/next/Documentation/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst +++ /dev/null @@ -1,7 +0,0 @@ -Updated documentation for :mod:`socketserver` to clarify the differences -between :class:`socketserver.BaseServer` and -:class:`socketserver.TCPServer`. Additionally, :class:`socketserver.BaseServer` -no longer assumes that all subclasses will add a ``socket`` attribute, and -:method:`socketserver.BaseServer.get_request` now raises ``NotImplementedError``. -It was previously absent and attempting to use it would generate an -``AttributeError``. diff --git a/Misc/NEWS.d/next/Library/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst b/Misc/NEWS.d/next/Library/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst new file mode 100644 index 00000000000000..21552d19a56291 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst @@ -0,0 +1,6 @@ +:method:`socketserver.BaseServer.get_request` now raises ``NotImplementedError``. +It was previously absent and attempting to use it would generate an +``AttributeError``. :class:`socketserver.BaseServer` no longer assumes that +all subclasses will add a ``socket`` attribute. Updated documentation to +clarify the differences between :class:`socketserver.BaseServer` and +:class:`socketserver.TCPServer`. From 910f4a75126dfdc888b250b7df72aba45fdaa295 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Mon, 16 Dec 2024 11:34:05 -0800 Subject: [PATCH 5/6] doc fix --- .../next/Library/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst b/Misc/NEWS.d/next/Library/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst index 21552d19a56291..ccb7131cab8d4f 100644 --- a/Misc/NEWS.d/next/Library/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst +++ b/Misc/NEWS.d/next/Library/2024-12-15-14-08-40.gh-issue-127209.17oPD0.rst @@ -1,4 +1,4 @@ -:method:`socketserver.BaseServer.get_request` now raises ``NotImplementedError``. +:meth:`socketserver.BaseServer.get_request` now raises ``NotImplementedError``. It was previously absent and attempting to use it would generate an ``AttributeError``. :class:`socketserver.BaseServer` no longer assumes that all subclasses will add a ``socket`` attribute. Updated documentation to From 325ec83904a583edfa7d6ca2c7dbeaf477e33e11 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Sun, 12 Jan 2025 15:32:00 -0800 Subject: [PATCH 6/6] Update Doc/library/socketserver.rst Co-authored-by: Peter Bierma --- Doc/library/socketserver.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/socketserver.rst b/Doc/library/socketserver.rst index 1c670995e194c1..b7105008a7b619 100644 --- a/Doc/library/socketserver.rst +++ b/Doc/library/socketserver.rst @@ -49,7 +49,7 @@ There are four basic concrete server classes: .. attribute:: socket - The socket object on which the server will listen for incoming requests. + The :class:`socket.socket` object on which the server will listen for incoming requests. .. attribute:: allow_reuse_address