Issue #26404: Add context manager to socketserver, by Aviv Palivoda

This commit is contained in:
Martin Panter 2016-04-13 00:36:52 +00:00
parent 7258176c68
commit 0cab9c1eba
11 changed files with 125 additions and 103 deletions

View File

@ -375,10 +375,9 @@ the current directory::
Handler = http.server.SimpleHTTPRequestHandler Handler = http.server.SimpleHTTPRequestHandler
httpd = socketserver.TCPServer(("", PORT), Handler) with socketserver.TCPServer(("", PORT), Handler) as httpd:
print("serving at port", PORT)
print("serving at port", PORT) httpd.serve_forever()
httpd.serve_forever()
.. _http-server-cli: .. _http-server-cli:

View File

@ -52,11 +52,12 @@ handler class by subclassing the :class:`BaseRequestHandler` class and
overriding its :meth:`~BaseRequestHandler.handle` method; overriding its :meth:`~BaseRequestHandler.handle` method;
this method will process incoming this method will process incoming
requests. Second, you must instantiate one of the server classes, passing it 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.handle_request` or
:meth:`~BaseServer.serve_forever` method of the server object to :meth:`~BaseServer.serve_forever` method of the server object to
process one or many requests. Finally, call :meth:`~BaseServer.server_close` 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, When inheriting from :class:`ThreadingMixIn` for threaded connection behavior,
you should explicitly declare how you want your threads to behave on an abrupt 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`. 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 Request Handler Objects
----------------------- -----------------------
@ -433,11 +439,10 @@ This is the server side::
HOST, PORT = "localhost", 9999 HOST, PORT = "localhost", 9999
# Create the server, binding to localhost on port 9999 # Create the server, binding to localhost on port 9999
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Activate the server; this will keep running until you
# Activate the server; this will keep running until you # interrupt the program with Ctrl-C
# interrupt the program with Ctrl-C server.serve_forever()
server.serve_forever()
An alternative request handler class that makes use of streams (file-like An alternative request handler class that makes use of streams (file-like
objects that simplify communication by providing the standard file interface):: objects that simplify communication by providing the standard file interface)::
@ -529,8 +534,8 @@ This is the server side::
if __name__ == "__main__": if __name__ == "__main__":
HOST, PORT = "localhost", 9999 HOST, PORT = "localhost", 9999
server = socketserver.UDPServer((HOST, PORT), MyUDPHandler) with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
server.serve_forever() server.serve_forever()
This is the client side:: This is the client side::
@ -592,22 +597,22 @@ An example for the :class:`ThreadingMixIn` class::
HOST, PORT = "localhost", 0 HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler) server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
ip, port = server.server_address with server:
ip, port = server.server_address
# Start a thread with the server -- that thread will then start one # Start a thread with the server -- that thread will then start one
# more thread for each request # more thread for each request
server_thread = threading.Thread(target=server.serve_forever) server_thread = threading.Thread(target=server.serve_forever)
# Exit the server thread when the main thread terminates # Exit the server thread when the main thread terminates
server_thread.daemon = True server_thread.daemon = True
server_thread.start() server_thread.start()
print("Server loop running in thread:", server_thread.name) print("Server loop running in thread:", server_thread.name)
client(ip, port, "Hello World 1") client(ip, port, "Hello World 1")
client(ip, port, "Hello World 2") client(ip, port, "Hello World 2")
client(ip, port, "Hello World 3") client(ip, port, "Hello World 3")
server.shutdown() server.shutdown()
server.server_close()
The output of the example should look something like this:: The output of the example should look something like this::

View File

@ -131,9 +131,9 @@ parameter expect a WSGI-compliant dictionary to be supplied; please see
for key, value in environ.items()] for key, value in environ.items()]
return ret return ret
httpd = make_server('', 8000, simple_app) with make_server('', 8000, simple_app) as httpd:
print("Serving on port 8000...") print("Serving on port 8000...")
httpd.serve_forever() httpd.serve_forever()
In addition to the environment functions above, the :mod:`wsgiref.util` module 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 from wsgiref.simple_server import make_server, demo_app
httpd = make_server('', 8000, demo_app) with make_server('', 8000, demo_app) as httpd:
print("Serving HTTP on port 8000...") print("Serving HTTP on port 8000...")
# Respond to requests until process is killed # Respond to requests until process is killed
httpd.serve_forever() httpd.serve_forever()
# Alternative: serve one request, then exit # Alternative: serve one request, then exit
httpd.handle_request() httpd.handle_request()
.. function:: demo_app(environ, start_response) .. function:: demo_app(environ, start_response)
@ -430,9 +430,9 @@ Paste" library.
# This is the application wrapped in a validator # This is the application wrapped in a validator
validator_app = validator(simple_app) validator_app = validator(simple_app)
httpd = make_server('', 8000, validator_app) with make_server('', 8000, validator_app) as httpd:
print("Listening on port 8000....") print("Listening on port 8000....")
httpd.serve_forever() httpd.serve_forever()
:mod:`wsgiref.handlers` -- server/gateway base classes :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 # The returned object is going to be printed
return [b"Hello World"] return [b"Hello World"]
httpd = make_server('', 8000, hello_world_app) with make_server('', 8000, hello_world_app) as httpd:
print("Serving on port 8000...") print("Serving on port 8000...")
# Serve until process is killed # Serve until process is killed
httpd.serve_forever() httpd.serve_forever()

View File

@ -147,29 +147,29 @@ Server code::
rpc_paths = ('/RPC2',) rpc_paths = ('/RPC2',)
# Create server # Create server
server = SimpleXMLRPCServer(("localhost", 8000), with SimpleXMLRPCServer(("localhost", 8000),
requestHandler=RequestHandler) requestHandler=RequestHandler) as server:
server.register_introspection_functions() server.register_introspection_functions()
# Register pow() function; this will use the value of # Register pow() function; this will use the value of
# pow.__name__ as the name, which is just 'pow'. # pow.__name__ as the name, which is just 'pow'.
server.register_function(pow) server.register_function(pow)
# Register a function under a different name # Register a function under a different name
def adder_function(x,y): def adder_function(x,y):
return x + y return x + y
server.register_function(adder_function, 'add') server.register_function(adder_function, 'add')
# Register an instance; all the methods of the instance are # Register an instance; all the methods of the instance are
# published as XML-RPC methods (in this case, just 'mul'). # published as XML-RPC methods (in this case, just 'mul').
class MyFuncs: class MyFuncs:
def mul(self, x, y): def mul(self, x, y):
return x * y return x * y
server.register_instance(MyFuncs()) server.register_instance(MyFuncs())
# Run the server's main loop # Run the server's main loop
server.serve_forever() server.serve_forever()
The following client code will call the methods made available by the preceding The following client code will call the methods made available by the preceding
server:: server::
@ -206,18 +206,17 @@ a server allowing dotted names and registering a multicall function.
def getCurrentTime(): def getCurrentTime():
return datetime.datetime.now() return datetime.datetime.now()
server = SimpleXMLRPCServer(("localhost", 8000)) with SimpleXMLRPCServer(("localhost", 8000)) as server:
server.register_function(pow) server.register_function(pow)
server.register_function(lambda x,y: x+y, 'add') server.register_function(lambda x,y: x+y, 'add')
server.register_instance(ExampleService(), allow_dotted_names=True) server.register_instance(ExampleService(), allow_dotted_names=True)
server.register_multicall_functions() server.register_multicall_functions()
print('Serving XML-RPC on localhost port 8000') print('Serving XML-RPC on localhost port 8000')
try: try:
server.serve_forever() server.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.") print("\nKeyboard interrupt received, exiting.")
server.server_close() sys.exit(0)
sys.exit(0)
This ExampleService demo can be invoked from the command line:: This ExampleService demo can be invoked from the command line::

View 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`). (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 telnetlib
--------- ---------

View File

@ -1175,16 +1175,14 @@ def test(HandlerClass=BaseHTTPRequestHandler,
server_address = (bind, port) server_address = (bind, port)
HandlerClass.protocol_version = protocol HandlerClass.protocol_version = protocol
httpd = ServerClass(server_address, HandlerClass) with ServerClass(server_address, HandlerClass) as httpd:
sa = httpd.socket.getsockname()
sa = httpd.socket.getsockname() print("Serving HTTP on", sa[0], "port", sa[1], "...")
print("Serving HTTP on", sa[0], "port", sa[1], "...") try:
try: httpd.serve_forever()
httpd.serve_forever() except KeyboardInterrupt:
except KeyboardInterrupt: print("\nKeyboard interrupt received, exiting.")
print("\nKeyboard interrupt received, exiting.") sys.exit(0)
httpd.server_close()
sys.exit(0)
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()

View File

@ -378,6 +378,12 @@ class BaseServer:
traceback.print_exc() traceback.print_exc()
print('-'*40, file=sys.stderr) print('-'*40, file=sys.stderr)
def __enter__(self):
return self
def __exit__(self, *args):
self.server_close()
class TCPServer(BaseServer): class TCPServer(BaseServer):

View File

@ -104,7 +104,6 @@ class SocketServerTest(unittest.TestCase):
class MyServer(svrcls): class MyServer(svrcls):
def handle_error(self, request, client_address): def handle_error(self, request, client_address):
self.close_request(request) self.close_request(request)
self.server_close()
raise raise
class MyHandler(hdlrbase): class MyHandler(hdlrbase):
@ -280,6 +279,12 @@ class SocketServerTest(unittest.TestCase):
socketserver.TCPServer((HOST, -1), socketserver.TCPServer((HOST, -1),
socketserver.StreamRequestHandler) 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): class ErrorHandlerTest(unittest.TestCase):
"""Test that the servers pass normal exceptions from the handler to """Test that the servers pass normal exceptions from the handler to

View File

@ -156,10 +156,9 @@ def make_server(
if __name__ == '__main__': if __name__ == '__main__':
httpd = make_server('', 8000, demo_app) with make_server('', 8000, demo_app) as httpd:
sa = httpd.socket.getsockname() sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...") print("Serving HTTP on", sa[0], "port", sa[1], "...")
import webbrowser import webbrowser
webbrowser.open('http://localhost:8000/xyz?abc') webbrowser.open('http://localhost:8000/xyz?abc')
httpd.handle_request() # serve one request, then exit httpd.handle_request() # serve one request, then exit
httpd.server_close()

View File

@ -971,16 +971,15 @@ if __name__ == '__main__':
def getCurrentTime(): def getCurrentTime():
return datetime.datetime.now() return datetime.datetime.now()
server = SimpleXMLRPCServer(("localhost", 8000)) with SimpleXMLRPCServer(("localhost", 8000)) as server:
server.register_function(pow) server.register_function(pow)
server.register_function(lambda x,y: x+y, 'add') server.register_function(lambda x,y: x+y, 'add')
server.register_instance(ExampleService(), allow_dotted_names=True) server.register_instance(ExampleService(), allow_dotted_names=True)
server.register_multicall_functions() server.register_multicall_functions()
print('Serving XML-RPC on localhost port 8000') print('Serving XML-RPC on localhost port 8000')
print('It is advisable to run this example server within a secure, closed network.') print('It is advisable to run this example server within a secure, closed network.')
try: try:
server.serve_forever() server.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.") print("\nKeyboard interrupt received, exiting.")
server.server_close() sys.exit(0)
sys.exit(0)

View File

@ -240,6 +240,8 @@ Core and Builtins
Library 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 - 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 more than 1,024 bytes: call ``getrandom()`` multiple times with a limit of
1024 bytes per call. 1024 bytes per call.