From 0b9db4a71376f97837bb3c081ff6b9396f66d786 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Tue, 24 Nov 2009 17:17:57 -0500 Subject: [PATCH] Deprecated tcp_server, added an accept loop example, fixed up two sites that still used tcp_server. --- eventlet/api.py | 21 ++++++++++------- eventlet/backdoor.py | 31 ++++++++++-------------- eventlet/green/socket.py | 2 ++ examples/accept_loop.py | 51 ++++++++++++++++++++++++++++++++++++++++ tests/api_test.py | 15 ++++++++---- 5 files changed, 89 insertions(+), 31 deletions(-) create mode 100644 examples/accept_loop.py diff --git a/eventlet/api.py b/eventlet/api.py index 98bc0d8..445b134 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -8,6 +8,8 @@ import threading from eventlet.support import greenlets as greenlet +import warnings + __all__ = [ 'call_after', 'exc_after', 'getcurrent', 'get_default_hub', 'get_hub', 'GreenletExit', 'kill', 'sleep', 'spawn', 'spew', 'switch', @@ -33,10 +35,6 @@ def tcp_listener(address, backlog=50): Listen on the given ``(ip, port)`` *address* with a TCP socket. Returns a socket object on which one should call ``accept()`` to accept a connection on the newly bound socket. - - Generally, the returned socket will be passed to :func:`tcp_server`, which - accepts connections forever and spawns greenlets for each incoming - connection. """ from eventlet import greenio, util socket = greenio.GreenSocket(util.tcp_socket()) @@ -52,10 +50,6 @@ def ssl_listener(address, certificate, private_key): Returns a socket object on which one should call ``accept()`` to accept a connection on the newly bound socket. - - Generally, the returned socket will be passed to - :func:`~eventlet.api.tcp_server`, which accepts connections forever and - spawns greenlets for each incoming connection. """ from eventlet import util socket = util.wrap_ssl(util.tcp_socket(), certificate, private_key, True) @@ -76,6 +70,14 @@ def connect_tcp(address, localaddr=None): def tcp_server(listensocket, server, *args, **kw): """ + **Deprecated** Please write your own accept loop instead, like this:: + + while True: + api.spawn(server, listensocket.accept(), ) + + A more complex accept loop can be found in ``examples/accept_loop.py``. + + *Original documentation:* Given a socket, accept connections forever, spawning greenlets and executing *server* for each new incoming connection. When *server* returns False, the :func:`tcp_server()` greenlet will end. @@ -85,6 +87,9 @@ def tcp_server(listensocket, server, *args, **kw): :param \*args: The positional arguments to pass to *server*. :param \*\*kw: The keyword arguments to pass to *server*. """ + warnings.warn("tcp_server is deprecated, please write your own "\ + "accept loop instead (see examples/accept_loop.py)", + DeprecationWarning, stacklevel=2) working = [True] try: while working[0] is not False: diff --git a/eventlet/backdoor.py b/eventlet/backdoor.py index f9b141c..fd661e7 100644 --- a/eventlet/backdoor.py +++ b/eventlet/backdoor.py @@ -1,5 +1,6 @@ import socket import sys +import errno from code import InteractiveConsole from eventlet import api @@ -68,33 +69,27 @@ class SocketConsole(greenlets.greenlet): def backdoor_server(server, locals=None): - print "backdoor listening on %s:%s" % server.getsockname() + """ Runs a backdoor server on the socket, accepting connections and + running backdoor consoles for each client that connects. + """ + print "backdoor server listening on %s:%s" % server.getsockname() try: try: while True: - (conn, (host, port)) = server.accept() - print "backdoor connected to %s:%s" % (host, port) - fl = conn.makeGreenFile("rw") - fl.newlines = '\n' - greenlet = SocketConsole(fl, (host, port), locals) - hub = api.get_hub() - hub.schedule_call_global(0, greenlet.switch) + socketpair = server.accept() + backdoor(socketpair, locals) except socket.error, e: # Broken pipe means it was shutdown - if e[0] != 32: + if e[0] != errno.EPIPE: raise finally: server.close() def backdoor((conn, addr), locals=None): - """ - Use this with tcp_server like so:: - - api.tcp_server( - api.tcp_listener(('127.0.0.1', 9000)), - backdoor.backdoor, - {}) + """Sets up an interactive console on a socket with a connected client. + This does not block the caller, as it spawns a new greenlet to handle + the console. """ host, port = addr print "backdoor to %s:%s" % (host, port) @@ -106,7 +101,5 @@ def backdoor((conn, addr), locals=None): if __name__ == '__main__': - api.tcp_server(api.tcp_listener(('127.0.0.1', 9000)), - backdoor, - {}) + backdoor_server(api.tcp_listener(('127.0.0.1', 9000)), {}) diff --git a/eventlet/green/socket.py b/eventlet/green/socket.py index 973220e..cbef830 100644 --- a/eventlet/green/socket.py +++ b/eventlet/green/socket.py @@ -130,12 +130,14 @@ class GreenSSLObject(object): try: + # >= Python 2.6 from eventlet.green import ssl def ssl(sock, certificate=None, private_key=None): warnings.warn("socket.ssl() is deprecated. Use ssl.wrap_socket() instead.", DeprecationWarning, stacklevel=2) return ssl.sslwrap_simple(sock, keyfile, certfile) except ImportError: + # <= Python 2.5 compatibility def ssl(sock, certificate=None, private_key=None): from eventlet import util wrapped = util.wrap_ssl(sock, certificate, private_key) diff --git a/examples/accept_loop.py b/examples/accept_loop.py new file mode 100644 index 0000000..9664881 --- /dev/null +++ b/examples/accept_loop.py @@ -0,0 +1,51 @@ +"""This is a simple echo server that demonstrates an accept loop. To use it, +run this script and then run 'telnet localhost 6011' in a different terminal. + +If you send an empty line to the echo server it will close the connection while +leaving the server running. If you send the word "shutdown" to the echo server +it will gracefully exit, terminating any other open connections. + +The actual accept loop logic is fully contained within the run_accept_loop +function. Everything else is setup. +""" + +from eventlet.green import socket +from eventlet.api import spawn + +class Acceptor(object): + def __init__(self, port=6011): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.setsockopt( + socket.SOL_SOCKET, + socket.SO_REUSEADDR, 1) + self.sock.bind(('localhost', port)) + self.sock.listen(50) + self.sock.settimeout(0.5) + self.done = False + + def run_accept_loop(self): + while not self.done: + try: + spawn(self.handle_one_client, self.sock.accept()) + except socket.timeout: + pass + + def handle_one_client(self, sockpair): + sock, addr = sockpair + print "Accepted client", addr + fd = sock.makefile() + line = fd.readline() + while line.strip(): + fd.write(line) + fd.flush() + if line.startswith("shutdown"): + self.done = True + print "Received shutdown" + break + line = fd.readline() + print "Done with client", addr + +if __name__ == "__main__": + a = Acceptor() + a.run_accept_loop() + print "Exiting" \ No newline at end of file diff --git a/tests/api_test.py b/tests/api_test.py index 9bf6258..99acb3a 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -91,7 +91,12 @@ class TestApi(TestCase): check_hub() - def test_server(self): + def test_tcp_server(self): + import warnings + # disabling tcp_server warnings because we're testing tcp_server here + warnings.filterwarnings(action = 'ignore', + message='.*tcp_server.*', + category=DeprecationWarning) connected = [] server = api.tcp_listener(('0.0.0.0', 0)) bound_port = server.getsockname()[1] @@ -134,8 +139,10 @@ class TestApi(TestCase): bound_port = server.getsockname()[1] done = [False] - def client_connected((conn, addr)): - conn.close() + def client_closer(sock): + while True: + (conn, addr) = sock.accept() + conn.close() def go(): client = util.tcp_socket() @@ -153,7 +160,7 @@ class TestApi(TestCase): api.call_after(0, go) - server_coro = api.spawn(api.tcp_server, server, client_connected) + server_coro = api.spawn(client_closer, server) while not done[0]: api.sleep(0) api.kill(server_coro)