Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0cab9c1

Browse files
committed
Issue #26404: Add context manager to socketserver, by Aviv Palivoda
1 parent 7258176 commit 0cab9c1

11 files changed

Lines changed: 128 additions & 106 deletions

File tree

Doc/library/http.server.rst

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,10 +375,9 @@ the current directory::
375375

376376
Handler = http.server.SimpleHTTPRequestHandler
377377

378-
httpd = socketserver.TCPServer(("", PORT), Handler)
379-
380-
print("serving at port", PORT)
381-
httpd.serve_forever()
378+
with socketserver.TCPServer(("", PORT), Handler) as httpd:
379+
print("serving at port", PORT)
380+
httpd.serve_forever()
382381

383382
.. _http-server-cli:
384383

Doc/library/socketserver.rst

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,12 @@ handler class by subclassing the :class:`BaseRequestHandler` class and
5252
overriding its :meth:`~BaseRequestHandler.handle` method;
5353
this method will process incoming
5454
requests. Second, you must instantiate one of the server classes, passing it
55-
the server's address and the request handler class. Then call the
55+
the server's address and the request handler class. It is recommended to use
56+
the server in a :keyword:`with` statement. Then call the
5657
:meth:`~BaseServer.handle_request` or
5758
:meth:`~BaseServer.serve_forever` method of the server object to
5859
process one or many requests. Finally, call :meth:`~BaseServer.server_close`
59-
to close the socket.
60+
to close the socket (unless you used a :keyword:`with` statement).
6061

6162
When inheriting from :class:`ThreadingMixIn` for threaded connection behavior,
6263
you should explicitly declare how you want your threads to behave on an abrupt
@@ -353,6 +354,11 @@ Server Objects
353354
default implementation always returns :const:`True`.
354355

355356

357+
.. versionchanged:: 3.6
358+
Support for the :term:`context manager` protocol was added. Exiting the
359+
context manager is equivalent to calling :meth:`server_close`.
360+
361+
356362
Request Handler Objects
357363
-----------------------
358364

@@ -433,11 +439,10 @@ This is the server side::
433439
HOST, PORT = "localhost", 9999
434440

435441
# Create the server, binding to localhost on port 9999
436-
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
437-
438-
# Activate the server; this will keep running until you
439-
# interrupt the program with Ctrl-C
440-
server.serve_forever()
442+
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
443+
# Activate the server; this will keep running until you
444+
# interrupt the program with Ctrl-C
445+
server.serve_forever()
441446

442447
An alternative request handler class that makes use of streams (file-like
443448
objects that simplify communication by providing the standard file interface)::
@@ -529,8 +534,8 @@ This is the server side::
529534

530535
if __name__ == "__main__":
531536
HOST, PORT = "localhost", 9999
532-
server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
533-
server.serve_forever()
537+
with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
538+
server.serve_forever()
534539

535540
This is the client side::
536541

@@ -592,22 +597,22 @@ An example for the :class:`ThreadingMixIn` class::
592597
HOST, PORT = "localhost", 0
593598

594599
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
595-
ip, port = server.server_address
596-
597-
# Start a thread with the server -- that thread will then start one
598-
# more thread for each request
599-
server_thread = threading.Thread(target=server.serve_forever)
600-
# Exit the server thread when the main thread terminates
601-
server_thread.daemon = True
602-
server_thread.start()
603-
print("Server loop running in thread:", server_thread.name)
604-
605-
client(ip, port, "Hello World 1")
606-
client(ip, port, "Hello World 2")
607-
client(ip, port, "Hello World 3")
608-
609-
server.shutdown()
610-
server.server_close()
600+
with server:
601+
ip, port = server.server_address
602+
603+
# Start a thread with the server -- that thread will then start one
604+
# more thread for each request
605+
server_thread = threading.Thread(target=server.serve_forever)
606+
# Exit the server thread when the main thread terminates
607+
server_thread.daemon = True
608+
server_thread.start()
609+
print("Server loop running in thread:", server_thread.name)
610+
611+
client(ip, port, "Hello World 1")
612+
client(ip, port, "Hello World 2")
613+
client(ip, port, "Hello World 3")
614+
615+
server.shutdown()
611616

612617

613618
The output of the example should look something like this::

Doc/library/wsgiref.rst

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,9 @@ parameter expect a WSGI-compliant dictionary to be supplied; please see
131131
for key, value in environ.items()]
132132
return ret
133133

134-
httpd = make_server('', 8000, simple_app)
135-
print("Serving on port 8000...")
136-
httpd.serve_forever()
134+
with make_server('', 8000, simple_app) as httpd:
135+
print("Serving on port 8000...")
136+
httpd.serve_forever()
137137

138138

139139
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
283283

284284
from wsgiref.simple_server import make_server, demo_app
285285

286-
httpd = make_server('', 8000, demo_app)
287-
print("Serving HTTP on port 8000...")
286+
with make_server('', 8000, demo_app) as httpd:
287+
print("Serving HTTP on port 8000...")
288288

289-
# Respond to requests until process is killed
290-
httpd.serve_forever()
289+
# Respond to requests until process is killed
290+
httpd.serve_forever()
291291

292-
# Alternative: serve one request, then exit
293-
httpd.handle_request()
292+
# Alternative: serve one request, then exit
293+
httpd.handle_request()
294294

295295

296296
.. function:: demo_app(environ, start_response)
@@ -430,9 +430,9 @@ Paste" library.
430430
# This is the application wrapped in a validator
431431
validator_app = validator(simple_app)
432432

433-
httpd = make_server('', 8000, validator_app)
434-
print("Listening on port 8000....")
435-
httpd.serve_forever()
433+
with make_server('', 8000, validator_app) as httpd:
434+
print("Listening on port 8000....")
435+
httpd.serve_forever()
436436

437437

438438
:mod:`wsgiref.handlers` -- server/gateway base classes
@@ -769,8 +769,8 @@ This is a working "Hello World" WSGI application::
769769
# The returned object is going to be printed
770770
return [b"Hello World"]
771771

772-
httpd = make_server('', 8000, hello_world_app)
773-
print("Serving on port 8000...")
772+
with make_server('', 8000, hello_world_app) as httpd:
773+
print("Serving on port 8000...")
774774

775-
# Serve until process is killed
776-
httpd.serve_forever()
775+
# Serve until process is killed
776+
httpd.serve_forever()

Doc/library/xmlrpc.server.rst

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -147,29 +147,29 @@ Server code::
147147
rpc_paths = ('/RPC2',)
148148

149149
# Create server
150-
server = SimpleXMLRPCServer(("localhost", 8000),
151-
requestHandler=RequestHandler)
152-
server.register_introspection_functions()
150+
with SimpleXMLRPCServer(("localhost", 8000),
151+
requestHandler=RequestHandler) as server:
152+
server.register_introspection_functions()
153153

154-
# Register pow() function; this will use the value of
155-
# pow.__name__ as the name, which is just 'pow'.
156-
server.register_function(pow)
154+
# Register pow() function; this will use the value of
155+
# pow.__name__ as the name, which is just 'pow'.
156+
server.register_function(pow)
157157

158-
# Register a function under a different name
159-
def adder_function(x,y):
160-
return x + y
161-
server.register_function(adder_function, 'add')
158+
# Register a function under a different name
159+
def adder_function(x,y):
160+
return x + y
161+
server.register_function(adder_function, 'add')
162162

163-
# Register an instance; all the methods of the instance are
164-
# published as XML-RPC methods (in this case, just 'mul').
165-
class MyFuncs:
166-
def mul(self, x, y):
167-
return x * y
163+
# Register an instance; all the methods of the instance are
164+
# published as XML-RPC methods (in this case, just 'mul').
165+
class MyFuncs:
166+
def mul(self, x, y):
167+
return x * y
168168

169-
server.register_instance(MyFuncs())
169+
server.register_instance(MyFuncs())
170170

171-
# Run the server's main loop
172-
server.serve_forever()
171+
# Run the server's main loop
172+
server.serve_forever()
173173

174174
The following client code will call the methods made available by the preceding
175175
server::
@@ -206,18 +206,17 @@ a server allowing dotted names and registering a multicall function.
206206
def getCurrentTime():
207207
return datetime.datetime.now()
208208

209-
server = SimpleXMLRPCServer(("localhost", 8000))
210-
server.register_function(pow)
211-
server.register_function(lambda x,y: x+y, 'add')
212-
server.register_instance(ExampleService(), allow_dotted_names=True)
213-
server.register_multicall_functions()
214-
print('Serving XML-RPC on localhost port 8000')
215-
try:
216-
server.serve_forever()
217-
except KeyboardInterrupt:
218-
print("\nKeyboard interrupt received, exiting.")
219-
server.server_close()
220-
sys.exit(0)
209+
with SimpleXMLRPCServer(("localhost", 8000)) as server:
210+
server.register_function(pow)
211+
server.register_function(lambda x,y: x+y, 'add')
212+
server.register_instance(ExampleService(), allow_dotted_names=True)
213+
server.register_multicall_functions()
214+
print('Serving XML-RPC on localhost port 8000')
215+
try:
216+
server.serve_forever()
217+
except KeyboardInterrupt:
218+
print("\nKeyboard interrupt received, exiting.")
219+
sys.exit(0)
221220

222221
This ExampleService demo can be invoked from the command line::
223222

Doc/whatsnew/3.6.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,16 @@ you may now specify file paths on top of directories (e.g. zip files).
259259
(Contributed by Wolfgang Langner in :issue:`26587`).
260260

261261

262+
socketserver
263+
------------
264+
265+
Servers based on the :mod:`socketserver` module, including those
266+
defined in :mod:`http.server`, :mod:`xmlrpc.server` and
267+
:mod:`wsgiref.simple_server`, now support the :term:`context manager`
268+
protocol.
269+
(Contributed by Aviv Palivoda in :issue:`26404`.)
270+
271+
262272
telnetlib
263273
---------
264274

Lib/http/server.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,16 +1175,14 @@ def test(HandlerClass=BaseHTTPRequestHandler,
11751175
server_address = (bind, port)
11761176

11771177
HandlerClass.protocol_version = protocol
1178-
httpd = ServerClass(server_address, HandlerClass)
1179-
1180-
sa = httpd.socket.getsockname()
1181-
print("Serving HTTP on", sa[0], "port", sa[1], "...")
1182-
try:
1183-
httpd.serve_forever()
1184-
except KeyboardInterrupt:
1185-
print("\nKeyboard interrupt received, exiting.")
1186-
httpd.server_close()
1187-
sys.exit(0)
1178+
with ServerClass(server_address, HandlerClass) as httpd:
1179+
sa = httpd.socket.getsockname()
1180+
print("Serving HTTP on", sa[0], "port", sa[1], "...")
1181+
try:
1182+
httpd.serve_forever()
1183+
except KeyboardInterrupt:
1184+
print("\nKeyboard interrupt received, exiting.")
1185+
sys.exit(0)
11881186

11891187
if __name__ == '__main__':
11901188
parser = argparse.ArgumentParser()

Lib/socketserver.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,12 @@ def handle_error(self, request, client_address):
378378
traceback.print_exc()
379379
print('-'*40, file=sys.stderr)
380380

381+
def __enter__(self):
382+
return self
383+
384+
def __exit__(self, *args):
385+
self.server_close()
386+
381387

382388
class TCPServer(BaseServer):
383389

Lib/test/test_socketserver.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ def make_server(self, addr, svrcls, hdlrbase):
104104
class MyServer(svrcls):
105105
def handle_error(self, request, client_address):
106106
self.close_request(request)
107-
self.server_close()
108107
raise
109108

110109
class MyHandler(hdlrbase):
@@ -280,6 +279,12 @@ def test_tcpserver_bind_leak(self):
280279
socketserver.TCPServer((HOST, -1),
281280
socketserver.StreamRequestHandler)
282281

282+
def test_context_manager(self):
283+
with socketserver.TCPServer((HOST, 0),
284+
socketserver.StreamRequestHandler) as server:
285+
pass
286+
self.assertEqual(-1, server.socket.fileno())
287+
283288

284289
class ErrorHandlerTest(unittest.TestCase):
285290
"""Test that the servers pass normal exceptions from the handler to

Lib/wsgiref/simple_server.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,9 @@ def make_server(
156156

157157

158158
if __name__ == '__main__':
159-
httpd = make_server('', 8000, demo_app)
160-
sa = httpd.socket.getsockname()
161-
print("Serving HTTP on", sa[0], "port", sa[1], "...")
162-
import webbrowser
163-
webbrowser.open('http://localhost:8000/xyz?abc')
164-
httpd.handle_request() # serve one request, then exit
165-
httpd.server_close()
159+
with make_server('', 8000, demo_app) as httpd:
160+
sa = httpd.socket.getsockname()
161+
print("Serving HTTP on", sa[0], "port", sa[1], "...")
162+
import webbrowser
163+
webbrowser.open('http://localhost:8000/xyz?abc')
164+
httpd.handle_request() # serve one request, then exit

Lib/xmlrpc/server.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -971,16 +971,15 @@ class currentTime:
971971
def getCurrentTime():
972972
return datetime.datetime.now()
973973

974-
server = SimpleXMLRPCServer(("localhost", 8000))
975-
server.register_function(pow)
976-
server.register_function(lambda x,y: x+y, 'add')
977-
server.register_instance(ExampleService(), allow_dotted_names=True)
978-
server.register_multicall_functions()
979-
print('Serving XML-RPC on localhost port 8000')
980-
print('It is advisable to run this example server within a secure, closed network.')
981-
try:
982-
server.serve_forever()
983-
except KeyboardInterrupt:
984-
print("\nKeyboard interrupt received, exiting.")
985-
server.server_close()
986-
sys.exit(0)
974+
with SimpleXMLRPCServer(("localhost", 8000)) as server:
975+
server.register_function(pow)
976+
server.register_function(lambda x,y: x+y, 'add')
977+
server.register_instance(ExampleService(), allow_dotted_names=True)
978+
server.register_multicall_functions()
979+
print('Serving XML-RPC on localhost port 8000')
980+
print('It is advisable to run this example server within a secure, closed network.')
981+
try:
982+
server.serve_forever()
983+
except KeyboardInterrupt:
984+
print("\nKeyboard interrupt received, exiting.")
985+
sys.exit(0)

0 commit comments

Comments
 (0)