]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #26404: Add context manager to socketserver, by Aviv Palivoda
authorMartin Panter <vadmium+py@gmail.com>
Wed, 13 Apr 2016 00:36:52 +0000 (00:36 +0000)
committerMartin Panter <vadmium+py@gmail.com>
Wed, 13 Apr 2016 00:36:52 +0000 (00:36 +0000)
Doc/library/http.server.rst
Doc/library/socketserver.rst
Doc/library/wsgiref.rst
Doc/library/xmlrpc.server.rst
Doc/whatsnew/3.6.rst
Lib/http/server.py
Lib/socketserver.py
Lib/test/test_socketserver.py
Lib/wsgiref/simple_server.py
Lib/xmlrpc/server.py
Misc/NEWS

index 0bde35b02a8f49d98573106368bc87537fb8c171..7ab249ab13733316521830ce8a368514a592c808 100644 (file)
@@ -375,10 +375,9 @@ the current directory::
 
    Handler = http.server.SimpleHTTPRequestHandler
 
-   httpd = socketserver.TCPServer(("", PORT), Handler)
-
-   print("serving at port", PORT)
-   httpd.serve_forever()
+   with socketserver.TCPServer(("", PORT), Handler) as httpd:
+       print("serving at port", PORT)
+       httpd.serve_forever()
 
 .. _http-server-cli:
 
index aaaa61e9d9755c8425edc1fe4d50a018abdbee12..e148d300691c9eca8060b6e005bc48f56e813f9e 100644 (file)
@@ -52,11 +52,12 @@ handler class by subclassing the :class:`BaseRequestHandler` class and
 overriding its :meth:`~BaseRequestHandler.handle` method;
 this method will process incoming
 requests.  Second, you must instantiate one of the server classes, passing it
-the server's address and the request handler class.  Then call the
+the server's address and the request handler class. It is recommended to use
+the server in a :keyword:`with` statement. Then call the
 :meth:`~BaseServer.handle_request` or
 :meth:`~BaseServer.serve_forever` method of the server object to
 process one or many requests.  Finally, call :meth:`~BaseServer.server_close`
-to close the socket.
+to close the socket (unless you used a :keyword:`with` statement).
 
 When inheriting from :class:`ThreadingMixIn` for threaded connection behavior,
 you should explicitly declare how you want your threads to behave on an abrupt
@@ -353,6 +354,11 @@ Server Objects
       default implementation always returns :const:`True`.
 
 
+   .. versionchanged:: 3.6
+      Support for the :term:`context manager` protocol was added.  Exiting the
+      context manager is equivalent to calling :meth:`server_close`.
+
+
 Request Handler Objects
 -----------------------
 
@@ -433,11 +439,10 @@ This is the server side::
        HOST, PORT = "localhost", 9999
 
        # Create the server, binding to localhost on port 9999
-       server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
-
-       # Activate the server; this will keep running until you
-       # interrupt the program with Ctrl-C
-       server.serve_forever()
+       with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
+           # Activate the server; this will keep running until you
+           # interrupt the program with Ctrl-C
+           server.serve_forever()
 
 An alternative request handler class that makes use of streams (file-like
 objects that simplify communication by providing the standard file interface)::
@@ -529,8 +534,8 @@ This is the server side::
 
    if __name__ == "__main__":
        HOST, PORT = "localhost", 9999
-       server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
-       server.serve_forever()
+       with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
+           server.serve_forever()
 
 This is the client side::
 
@@ -592,22 +597,22 @@ An example for the :class:`ThreadingMixIn` class::
        HOST, PORT = "localhost", 0
 
        server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
-       ip, port = server.server_address
-
-       # Start a thread with the server -- that thread will then start one
-       # more thread for each request
-       server_thread = threading.Thread(target=server.serve_forever)
-       # Exit the server thread when the main thread terminates
-       server_thread.daemon = True
-       server_thread.start()
-       print("Server loop running in thread:", server_thread.name)
-
-       client(ip, port, "Hello World 1")
-       client(ip, port, "Hello World 2")
-       client(ip, port, "Hello World 3")
-
-       server.shutdown()
-       server.server_close()
+       with server:
+           ip, port = server.server_address
+
+           # Start a thread with the server -- that thread will then start one
+           # more thread for each request
+           server_thread = threading.Thread(target=server.serve_forever)
+           # Exit the server thread when the main thread terminates
+           server_thread.daemon = True
+           server_thread.start()
+           print("Server loop running in thread:", server_thread.name)
+
+           client(ip, port, "Hello World 1")
+           client(ip, port, "Hello World 2")
+           client(ip, port, "Hello World 3")
+
+           server.shutdown()
 
 
 The output of the example should look something like this::
index 71607d690ac93dbdf5934b4dc45bb5ac2c43b6eb..8d6288556527d53f45d006068613f53a37321705 100644 (file)
@@ -131,9 +131,9 @@ parameter expect a WSGI-compliant dictionary to be supplied; please see
                  for key, value in environ.items()]
           return ret
 
-      httpd = make_server('', 8000, simple_app)
-      print("Serving on port 8000...")
-      httpd.serve_forever()
+      with make_server('', 8000, simple_app) as httpd:
+          print("Serving on port 8000...")
+          httpd.serve_forever()
 
 
 In addition to the environment functions above, the :mod:`wsgiref.util` module
@@ -283,14 +283,14 @@ request.  (E.g., using the :func:`shift_path_info` function from
 
       from wsgiref.simple_server import make_server, demo_app
 
-      httpd = make_server('', 8000, demo_app)
-      print("Serving HTTP on port 8000...")
+      with make_server('', 8000, demo_app) as httpd:
+          print("Serving HTTP on port 8000...")
 
-      # Respond to requests until process is killed
-      httpd.serve_forever()
+          # Respond to requests until process is killed
+          httpd.serve_forever()
 
-      # Alternative: serve one request, then exit
-      httpd.handle_request()
+          # Alternative: serve one request, then exit
+          httpd.handle_request()
 
 
 .. function:: demo_app(environ, start_response)
@@ -430,9 +430,9 @@ Paste" library.
       # This is the application wrapped in a validator
       validator_app = validator(simple_app)
 
-      httpd = make_server('', 8000, validator_app)
-      print("Listening on port 8000....")
-      httpd.serve_forever()
+      with make_server('', 8000, validator_app) as httpd:
+          print("Listening on port 8000....")
+          httpd.serve_forever()
 
 
 :mod:`wsgiref.handlers` -- server/gateway base classes
@@ -769,8 +769,8 @@ This is a working "Hello World" WSGI application::
        # The returned object is going to be printed
        return [b"Hello World"]
 
-   httpd = make_server('', 8000, hello_world_app)
-   print("Serving on port 8000...")
+   with make_server('', 8000, hello_world_app) as httpd:
+       print("Serving on port 8000...")
 
-   # Serve until process is killed
-   httpd.serve_forever()
+       # Serve until process is killed
+       httpd.serve_forever()
index 680db41f212bbb79c77db221a6b3133c2eb61793..ca80aab2a84c67360fb4c992c0eb0262095e585b 100644 (file)
@@ -147,29 +147,29 @@ Server code::
        rpc_paths = ('/RPC2',)
 
    # Create server
-   server = SimpleXMLRPCServer(("localhost", 8000),
-                               requestHandler=RequestHandler)
-   server.register_introspection_functions()
+   with SimpleXMLRPCServer(("localhost", 8000),
+                           requestHandler=RequestHandler) as server:
+       server.register_introspection_functions()
 
-   # Register pow() function; this will use the value of
-   # pow.__name__ as the name, which is just 'pow'.
-   server.register_function(pow)
+       # Register pow() function; this will use the value of
+       # pow.__name__ as the name, which is just 'pow'.
+       server.register_function(pow)
 
-   # Register a function under a different name
-   def adder_function(x,y):
-       return x + y
-   server.register_function(adder_function, 'add')
+       # Register a function under a different name
+       def adder_function(x,y):
+           return x + y
+       server.register_function(adder_function, 'add')
 
-   # Register an instance; all the methods of the instance are
-   # published as XML-RPC methods (in this case, just 'mul').
-   class MyFuncs:
-       def mul(self, x, y):
-           return x * y
+       # Register an instance; all the methods of the instance are
+       # published as XML-RPC methods (in this case, just 'mul').
+       class MyFuncs:
+           def mul(self, x, y):
+               return x * y
 
-   server.register_instance(MyFuncs())
+       server.register_instance(MyFuncs())
 
-   # Run the server's main loop
-   server.serve_forever()
+       # Run the server's main loop
+       server.serve_forever()
 
 The following client code will call the methods made available by the preceding
 server::
@@ -206,18 +206,17 @@ a server allowing dotted names and registering a multicall function.
             def getCurrentTime():
                 return datetime.datetime.now()
 
-    server = SimpleXMLRPCServer(("localhost", 8000))
-    server.register_function(pow)
-    server.register_function(lambda x,y: x+y, 'add')
-    server.register_instance(ExampleService(), allow_dotted_names=True)
-    server.register_multicall_functions()
-    print('Serving XML-RPC on localhost port 8000')
-    try:
-        server.serve_forever()
-    except KeyboardInterrupt:
-        print("\nKeyboard interrupt received, exiting.")
-        server.server_close()
-        sys.exit(0)
+    with SimpleXMLRPCServer(("localhost", 8000)) as server:
+        server.register_function(pow)
+        server.register_function(lambda x,y: x+y, 'add')
+        server.register_instance(ExampleService(), allow_dotted_names=True)
+        server.register_multicall_functions()
+        print('Serving XML-RPC on localhost port 8000')
+        try:
+            server.serve_forever()
+        except KeyboardInterrupt:
+            print("\nKeyboard interrupt received, exiting.")
+            sys.exit(0)
 
 This ExampleService demo can be invoked from the command line::
 
index 8bf284715915a4c356f4e11add1ab54f3053f240..ef10ef24d696f3a1a45fe600c2573ffff2f05005 100644 (file)
@@ -259,6 +259,16 @@ you may now specify file paths on top of directories (e.g. zip files).
 (Contributed by Wolfgang Langner in :issue:`26587`).
 
 
+socketserver
+------------
+
+Servers based on the :mod:`socketserver` module, including those
+defined in :mod:`http.server`, :mod:`xmlrpc.server` and
+:mod:`wsgiref.simple_server`, now support the :term:`context manager`
+protocol.
+(Contributed by Aviv Palivoda in :issue:`26404`.)
+
+
 telnetlib
 ---------
 
index fbee6a932de108a310a8bb7bd689cf9838a1eaa0..c1607b36b0bcd49d4d9c2e3bd7be1fed68071560 100644 (file)
@@ -1175,16 +1175,14 @@ def test(HandlerClass=BaseHTTPRequestHandler,
     server_address = (bind, port)
 
     HandlerClass.protocol_version = protocol
-    httpd = ServerClass(server_address, HandlerClass)
-
-    sa = httpd.socket.getsockname()
-    print("Serving HTTP on", sa[0], "port", sa[1], "...")
-    try:
-        httpd.serve_forever()
-    except KeyboardInterrupt:
-        print("\nKeyboard interrupt received, exiting.")
-        httpd.server_close()
-        sys.exit(0)
+    with ServerClass(server_address, HandlerClass) as httpd:
+        sa = httpd.socket.getsockname()
+        print("Serving HTTP on", sa[0], "port", sa[1], "...")
+        try:
+            httpd.serve_forever()
+        except KeyboardInterrupt:
+            print("\nKeyboard interrupt received, exiting.")
+            sys.exit(0)
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser()
index 2f39514eb952de0046f390d23a7e4fe5695e4aad..3e1f058f52b3dbd91c282b989cbb7ee00a93c9b1 100644 (file)
@@ -378,6 +378,12 @@ class BaseServer:
         traceback.print_exc()
         print('-'*40, file=sys.stderr)
 
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *args):
+        self.server_close()
+
 
 class TCPServer(BaseServer):
 
index 27fe01cee44aece3485946d81992fff3ce635c01..554c106f5d69c72aab36ff226c7ec59a3f8bf989 100644 (file)
@@ -104,7 +104,6 @@ class SocketServerTest(unittest.TestCase):
         class MyServer(svrcls):
             def handle_error(self, request, client_address):
                 self.close_request(request)
-                self.server_close()
                 raise
 
         class MyHandler(hdlrbase):
@@ -280,6 +279,12 @@ class SocketServerTest(unittest.TestCase):
                 socketserver.TCPServer((HOST, -1),
                                        socketserver.StreamRequestHandler)
 
+    def test_context_manager(self):
+        with socketserver.TCPServer((HOST, 0),
+                                    socketserver.StreamRequestHandler) as server:
+            pass
+        self.assertEqual(-1, server.socket.fileno())
+
 
 class ErrorHandlerTest(unittest.TestCase):
     """Test that the servers pass normal exceptions from the handler to
index 378b316bbd457c977f50fb4a6cfff43f90191033..1807c66a6baa7e8f338ca8ebef598f15c4600f55 100644 (file)
@@ -156,10 +156,9 @@ def make_server(
 
 
 if __name__ == '__main__':
-    httpd = make_server('', 8000, demo_app)
-    sa = httpd.socket.getsockname()
-    print("Serving HTTP on", sa[0], "port", sa[1], "...")
-    import webbrowser
-    webbrowser.open('http://localhost:8000/xyz?abc')
-    httpd.handle_request()  # serve one request, then exit
-    httpd.server_close()
+    with make_server('', 8000, demo_app) as httpd:
+        sa = httpd.socket.getsockname()
+        print("Serving HTTP on", sa[0], "port", sa[1], "...")
+        import webbrowser
+        webbrowser.open('http://localhost:8000/xyz?abc')
+        httpd.handle_request()  # serve one request, then exit
index 5b5bf7c405e6f5c075a15306d6841cab6e051f83..78728f271c1d55f722232f6847bc38cb2c8d5b93 100644 (file)
@@ -971,16 +971,15 @@ if __name__ == '__main__':
             def getCurrentTime():
                 return datetime.datetime.now()
 
-    server = SimpleXMLRPCServer(("localhost", 8000))
-    server.register_function(pow)
-    server.register_function(lambda x,y: x+y, 'add')
-    server.register_instance(ExampleService(), allow_dotted_names=True)
-    server.register_multicall_functions()
-    print('Serving XML-RPC on localhost port 8000')
-    print('It is advisable to run this example server within a secure, closed network.')
-    try:
-        server.serve_forever()
-    except KeyboardInterrupt:
-        print("\nKeyboard interrupt received, exiting.")
-        server.server_close()
-        sys.exit(0)
+    with SimpleXMLRPCServer(("localhost", 8000)) as server:
+        server.register_function(pow)
+        server.register_function(lambda x,y: x+y, 'add')
+        server.register_instance(ExampleService(), allow_dotted_names=True)
+        server.register_multicall_functions()
+        print('Serving XML-RPC on localhost port 8000')
+        print('It is advisable to run this example server within a secure, closed network.')
+        try:
+            server.serve_forever()
+        except KeyboardInterrupt:
+            print("\nKeyboard interrupt received, exiting.")
+            sys.exit(0)
index d98c184518c4e3f7cfb12bbe9c68a2d48f2d1059..a4109ab9b2235e708884e99183c905051e8cc310 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -240,6 +240,8 @@ Core and Builtins
 Library
 -------
 
+- Issue #26404: Add context manager to socketserver.  Patch by Aviv Palivoda.
+
 - Issue #26735: Fix :func:`os.urandom` on Solaris 11.3 and newer when reading
   more than 1,024 bytes: call ``getrandom()`` multiple times with a limit of
   1024 bytes per call.