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,8 +375,7 @@ the current directory::
Handler = http.server.SimpleHTTPRequestHandler
httpd = socketserver.TCPServer(("", PORT), Handler)
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print("serving at port", PORT)
httpd.serve_forever()

View 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,8 +439,7 @@ This is the server side::
HOST, PORT = "localhost", 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
# interrupt the program with Ctrl-C
server.serve_forever()
@ -529,7 +534,7 @@ This is the server side::
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
server.serve_forever()
This is the client side::
@ -592,6 +597,7 @@ An example for the :class:`ThreadingMixIn` class::
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# Start a thread with the server -- that thread will then start one
@ -607,7 +613,6 @@ An example for the :class:`ThreadingMixIn` class::
client(ip, port, "Hello World 3")
server.shutdown()
server.server_close()
The output of the example should look something like this::

View File

@ -131,7 +131,7 @@ 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)
with make_server('', 8000, simple_app) as httpd:
print("Serving on port 8000...")
httpd.serve_forever()
@ -283,7 +283,7 @@ 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)
with make_server('', 8000, demo_app) as httpd:
print("Serving HTTP on port 8000...")
# Respond to requests until process is killed
@ -430,7 +430,7 @@ Paste" library.
# This is the application wrapped in a validator
validator_app = validator(simple_app)
httpd = make_server('', 8000, validator_app)
with make_server('', 8000, validator_app) as httpd:
print("Listening on port 8000....")
httpd.serve_forever()
@ -769,7 +769,7 @@ 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)
with make_server('', 8000, hello_world_app) as httpd:
print("Serving on port 8000...")
# Serve until process is killed

View File

@ -147,8 +147,8 @@ Server code::
rpc_paths = ('/RPC2',)
# Create server
server = SimpleXMLRPCServer(("localhost", 8000),
requestHandler=RequestHandler)
with SimpleXMLRPCServer(("localhost", 8000),
requestHandler=RequestHandler) as server:
server.register_introspection_functions()
# Register pow() function; this will use the value of
@ -206,7 +206,7 @@ a server allowing dotted names and registering a multicall function.
def getCurrentTime():
return datetime.datetime.now()
server = SimpleXMLRPCServer(("localhost", 8000))
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)
@ -216,7 +216,6 @@ a server allowing dotted names and registering a multicall function.
server.serve_forever()
except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.")
server.server_close()
sys.exit(0)
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`).
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
---------

View File

@ -1175,15 +1175,13 @@ def test(HandlerClass=BaseHTTPRequestHandler,
server_address = (bind, port)
HandlerClass.protocol_version = protocol
httpd = ServerClass(server_address, HandlerClass)
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.")
httpd.server_close()
sys.exit(0)
if __name__ == '__main__':

View 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):

View 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

View File

@ -156,10 +156,9 @@ def make_server(
if __name__ == '__main__':
httpd = make_server('', 8000, demo_app)
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
httpd.server_close()

View File

@ -971,7 +971,7 @@ if __name__ == '__main__':
def getCurrentTime():
return datetime.datetime.now()
server = SimpleXMLRPCServer(("localhost", 8000))
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)
@ -982,5 +982,4 @@ if __name__ == '__main__':
server.serve_forever()
except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.")
server.server_close()
sys.exit(0)

View File

@ -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.