[Checkins] SVN: zc.ngi/trunk/ Added UDP support.
Jim Fulton
jim at zope.com
Fri May 22 18:57:50 EDT 2009
Log message for revision 100261:
Added UDP support.
Also, adjusted APIs to make them a bit cleaner:
- Now there's an IImplementation interface that defines the
callables an implementation must supply.
- Renamed 'connector' to 'connect'. The old name is still available.
Changed:
U zc.ngi/trunk/buildout.cfg
U zc.ngi/trunk/src/zc/ngi/README.txt
A zc.ngi/trunk/src/zc/ngi/async-udp.test
U zc.ngi/trunk/src/zc/ngi/async.py
U zc.ngi/trunk/src/zc/ngi/async.txt
U zc.ngi/trunk/src/zc/ngi/blocking.py
U zc.ngi/trunk/src/zc/ngi/blocking.txt
U zc.ngi/trunk/src/zc/ngi/interfaces.py
U zc.ngi/trunk/src/zc/ngi/message.py
U zc.ngi/trunk/src/zc/ngi/message.txt
U zc.ngi/trunk/src/zc/ngi/testing.py
U zc.ngi/trunk/src/zc/ngi/testing.test
U zc.ngi/trunk/src/zc/ngi/tests.py
U zc.ngi/trunk/src/zc/ngi/wordcount.py
-=-
Modified: zc.ngi/trunk/buildout.cfg
===================================================================
--- zc.ngi/trunk/buildout.cfg 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/buildout.cfg 2009-05-22 22:57:50 UTC (rev 100261)
@@ -1,8 +1,13 @@
[buildout]
develop = .
-parts = test
+parts = test py
[test]
recipe = zc.recipe.testrunner
eggs = zc.ngi [test]
+[py]
+recipe = zc.recipe.egg
+eggs = zc.ngi
+interpreter = py
+
Modified: zc.ngi/trunk/src/zc/ngi/README.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/README.txt 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/README.txt 2009-05-22 22:57:50 UTC (rev 100261)
@@ -13,30 +13,32 @@
There are several interfaces defined by the NGI:
+IImplementation
+ APIs for implementing and connecting to TCP servers and for
+ implemented and sending messages to UDP servers.
+
IConnection
Network connection implementation. This is the core interface that
applications interact with,
IConnectionHandler
- Application component that handles network input.
+ Application component that handles TCP network input.
-IConnector
- Create IConnection objects by making outgoing connections.
-
IClientConnectHandler
Application callback that handles successful or failed outgoing
- connections.
+ TCP connections.
-IListener
- Listen for incoming connections.
-
IServer
- Callback to handle incoming connections.
+ Application callback to handle incoming connections.
+IUDPHandler
+ Application callback to handle incoming UDP messages.
+
The interfaces are split between "implementation" and "application"
-interfaces. An implementation of the NGI provides IConnection,
-IConnector, and IListener. An application provides IConnectionHandler
-and one or both of IClientConnectHandler and IServer.
+interfaces. An implementation of the NGI provides Implementation,
+IConnection, IListener, and IUDPListener. An application provides
+IConnectionHandler and one or more of IClientConnectHandler,
+IServer, or IUDPHandler.
For more information, see interfaces.py.
@@ -68,12 +70,12 @@
>>> class EchoClient:
...
- ... def __init__(self, connector):
- ... self.connector = connector
+ ... def __init__(self, connect):
+ ... self.connect = connect
...
... def check(self, addr, strings):
... self.strings = strings
- ... self.connector(addr, self)
+ ... self.connect(addr, self)
...
... def connected(self, connection):
... for s in self.strings:
@@ -113,9 +115,9 @@
interfaces. More complex clients might implement these interfaces with
separate classes.
-We'll instantiate our client using the testing connector:
+We'll instantiate our client using the testing connect:
- >>> client = EchoClient(zc.ngi.testing.connector)
+ >>> client = EchoClient(zc.ngi.testing.connect)
Now we'll try to check a non-existent server:
@@ -126,7 +128,7 @@
fails. More complex applications might retry, waiting between attempts,
and so on.
-The testing connector always fails unless given a test connection
+The testing connect always fails unless given a test connection
ahead of time. We'll create a testing connection and register it so a
connection can succeed:
@@ -211,7 +213,7 @@
============================
Implementing network servers is very similar to implementing clients,
-except that a listener, rather than a connector is used. Let's
+except that a listener, rather than a connect is used. Let's
implement a simple echo server:
@@ -288,7 +290,7 @@
>>> connection = zc.ngi.testing.Connection()
>>> listener.connect(connection)
server connected
-
+
>>> list(listener.connections()) == [connection]
True
@@ -348,7 +350,7 @@
Traceback (most recent call last):
...
TypeError: Listener closed
-
+
And the handler will be called when all of the listener's connections
are closed:
@@ -362,7 +364,7 @@
===========
Test requests output data written to them. If output exceeds 50
-characters in length, it is wrapped by simply breaking the repr into
+characters in length, it is wrapped by simply breaking the repr into
50-characters parts:
>>> connection = zc.ngi.testing.Connection()
@@ -408,16 +410,16 @@
It is sometimes useful to connect a client handler and a server
handler. Listeners created with the zc.ngi.testing.listener class have a
-connector method that can be used to create connections to a server.
+connect method that can be used to create connections to a server.
Let's connect out echo server and client. First, we'll create out
server using the listener constructor:
>>> listener = zc.ngi.testing.listener(EchoServer)
-Then we'll use the connector method on the listener:
+Then we'll use the connect method on the listener:
- >>> client = EchoClient(listener.connector)
+ >>> client = EchoClient(listener.connect)
>>> client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
server connected
server got input: 'hello\n'
@@ -434,7 +436,7 @@
Below is an older API for connecting servers and clients in a
testing environment. The mechanisms defined above are prefered.
- The zc.ngi.testing.peer function can be used to create a
+ The zc.ngi.testing.peer function can be used to create a
connection to a peer handler. To illustrate, we'll set up an echo
client that connects to our echo server:
@@ -449,3 +451,34 @@
matched: world
matched: how are you?
server closed: closed
+
+UDP Support
+===========
+
+To send a UDP message, just use an implementations udp method:
+
+ >>> zc.ngi.testing.udp(('', 42), "hello")
+
+If there isn't a server listening, the call will effectively be
+ignored. This is UDP. :)
+
+ >>> def my_udp_handler(addr, data):
+ ... print 'from %r got %r' % (addr, data)
+
+ >>> listener = zc.ngi.testing.udp_listener(('', 42), my_udp_handler)
+
+ >>> zc.ngi.testing.udp(('', 42), "hello")
+ from '<test>' got 'hello'
+
+ >>> listener.close()
+ >>> zc.ngi.testing.udp(('', 42), "hello")
+
+For a handler is used if you don't pass a handler:
+
+ >>> listener = zc.ngi.testing.udp_listener(('', 43))
+ >>> zc.ngi.testing.udp(('', 43), "hello")
+ udp from '<test>' to ('', 43):
+ 'hello'
+
+ >>> listener.close()
+ >>> zc.ngi.testing.udp(('', 43), "hello")
Added: zc.ngi/trunk/src/zc/ngi/async-udp.test
===================================================================
--- zc.ngi/trunk/src/zc/ngi/async-udp.test (rev 0)
+++ zc.ngi/trunk/src/zc/ngi/async-udp.test 2009-05-22 22:57:50 UTC (rev 100261)
@@ -0,0 +1,23 @@
+async UDP support
+-----------------
+
+ >>> import zc.ngi.async, time
+
+ >>> zc.ngi.async.udp(('', 9644), 'test')
+
+ >>> def handler(addr, message):
+ ... print message
+
+ >>> listener = zc.ngi.async.udp_listener(('', 9644), handler)
+ >>> time.sleep(0.1)
+
+ >>> zc.ngi.async.udp(('', 9644), 'test'); time.sleep(0.1)
+ test
+
+ >>> zc.ngi.async.udp(('', 9644), 'test'); time.sleep(0.1)
+ test
+
+ >>> listener.close()
+ >>> time.sleep(0.1)
+
+ >>> zc.ngi.async.udp(('', 9644), 'test'); time.sleep(0.1)
Property changes on: zc.ngi/trunk/src/zc/ngi/async-udp.test
___________________________________________________________________
Added: svn:eol-style
+ native
Modified: zc.ngi/trunk/src/zc/ngi/async.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/async.py 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/async.py 2009-05-22 22:57:50 UTC (rev 100261)
@@ -330,23 +330,41 @@
def handle_expt(self):
self.handle_close('connection failed')
-class listener(asyncore.dispatcher):
+def connect(*args):
+ connector(*args)
+class BaseListener(asyncore.dispatcher):
+
+ def writable(self):
+ return False
+
+ def add_channel(self, map=None):
+ # work around file-dispatcher bug
+ assert (map is None) or (map is _map)
+ asyncore.dispatcher.add_channel(self, _map)
+
+ def handle_error(self):
+ reason = sys.exc_info()[1]
+ self.logger.exception('listener error')
+ self.close()
+
+class listener(BaseListener):
+
logger = logging.getLogger('zc.ngi.async.server')
def __init__(self, addr, handler):
- self.addr = addr
self.__handler = handler
self.__close_handler = None
self.__connections = {}
asyncore.dispatcher.__init__(self)
if isinstance(addr, str):
- self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ family = socket.AF_UNIX
else:
- self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ family = socket.AF_INET
+ self.create_socket(family, socket.SOCK_STREAM)
self.set_reuse_addr()
- self.logger.info("listening on %s", self.addr)
- self.bind(self.addr)
+ self.logger.info("listening on %r", self.addr)
+ self.bind(addr)
self.listen(255)
notify_select()
@@ -397,16 +415,47 @@
else:
self.__close_handler = handler
- def add_channel(self, map=None):
- # work around file-dispatcher bug
- assert (map is None) or (map is _map)
- asyncore.dispatcher.add_channel(self, _map)
+class udp_listener(BaseListener):
- def handle_error(self):
- reason = sys.exc_info()[1]
- self.logger.exception('listener error')
- self.close()
+ logger = logging.getLogger('zc.ngi.async.udpserver')
+ connected = True
+ def __init__(self, addr, handler, buffer_size=4096):
+ self.__handler = handler
+ self.__buffer_size = buffer_size
+ asyncore.dispatcher.__init__(self)
+ if isinstance(addr, str):
+ family = socket.AF_UNIX
+ else:
+ family = socket.AF_INET
+ self.create_socket(family, socket.SOCK_DGRAM)
+ self.set_reuse_addr()
+ self.logger.info("listening on udp %r", self.addr)
+ self.bind(addr)
+ notify_select()
+
+ def handle_read(self):
+ message, addr = self.recvfrom(self.__buffer_size)
+ self.__handler(addr, message)
+
+ def close(self):
+ self.del_channel(_map)
+ self.socket.close()
+
+# udp uses GIL to get thread-safe socket management
+_udp_socks = {socket.AF_INET: [], socket.AF_UNIX: []}
+def udp(address, message):
+ if isinstance(address, str):
+ family = socket.AF_UNIX
+ else:
+ family = socket.AF_INET
+ try:
+ sock = _udp_socks[family].pop()
+ except IndexError:
+ sock = socket.socket(family, socket.SOCK_DGRAM)
+ sock.sendto(message, address)
+ _udp_socks[family].append(sock)
+
# The following trigger code is greatly simplified from the Medusa
# trigger code.
Modified: zc.ngi/trunk/src/zc/ngi/async.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/async.txt 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/async.txt 2009-05-22 22:57:50 UTC (rev 100261)
@@ -41,7 +41,7 @@
>>> import threading
>>> addr = 'localhost', port
>>> threads = [threading.Thread(target=zc.ngi.wordcount.client_thread,
- ... args=(zc.ngi.async.connector, addr))
+ ... args=(zc.ngi.async.connect, addr))
... for i in range(200)]
>>> _ = [thread.start() for thread in threads]
@@ -54,7 +54,7 @@
we'll use the blocking interface:
>>> import zc.ngi.blocking
- >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connector,
+ >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connect,
... timeout=1.0)
>>> def hello(name):
... yield "hello\n"
@@ -109,7 +109,7 @@
Handler errors cause connections to be closed. To see this, we'll
send the server an error message, which foreces an error:
- >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connector,
+ >>> output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connect,
... timeout=1.0)
>>> output.write('E\0')
>>> input.read()
@@ -131,7 +131,7 @@
... event.set()
>>> handler = LameClientConnectionHandler()
- >>> _ = zc.ngi.async.connector(addr, handler)
+ >>> zc.ngi.async.connect(addr, handler)
>>> event.wait(1)
>>> print loghandler
@@ -158,7 +158,7 @@
... event.set()
>>> handler = LameClientConnectionHandler()
- >>> _ = zc.ngi.async.connector(addr, handler)
+ >>> zc.ngi.async.connect(addr, handler)
>>> event.wait(1)
>>> print loghandler
@@ -173,7 +173,7 @@
.. stop the server
- >>> zc.ngi.wordcount.stop_server_process(zc.ngi.async.connector, addr)
+ >>> zc.ngi.wordcount.stop_server_process(zc.ngi.async.connect, addr)
... # doctest: +ELLIPSIS
handle_input failed
Traceback (most recent call last):
Modified: zc.ngi/trunk/src/zc/ngi/blocking.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/blocking.py 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/blocking.py 2009-05-22 22:57:50 UTC (rev 100261)
@@ -32,16 +32,16 @@
"""An attempt to connect timed out.
"""
-def connect(address, connector, timeout=None):
- return _connector().connect(address, connector, timeout)
+def connect(address, connect, timeout=None):
+ return _connector().connect(address, connect, timeout)
class _connector:
failed = connection = None
- def connect(self, address, connector, timeout):
+ def connect(self, address, connect, timeout):
event = self.event = threading.Event()
- connector(address, self)
+ connect(address, self)
event.wait(timeout)
if self.failed is not None:
raise ConnectionFailed(self.failed)
Modified: zc.ngi/trunk/src/zc/ngi/blocking.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/blocking.txt 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/blocking.txt 2009-05-22 22:57:50 UTC (rev 100261)
@@ -18,12 +18,12 @@
>>> import zc.ngi.testing
>>> connector = zc.ngi.testing.peer(('localhost', 42),
... zc.ngi.wordcount.Server)
-
-The open function is called with an address and a connector:
-
+
+The open function is called with an address and a connect callable:
+
>>> import zc.ngi.blocking
>>> output, input = zc.ngi.blocking.open(('localhost', 42), connector)
-
+
The output file lets us send output to the server:
>>> output.write("Hello\n")
@@ -67,7 +67,7 @@
'4\n1 2 9\n'
If read is called without a size, it won't return until the server has
-closed the connection.
+closed the connection.
In this example, we've been careful to only read as much data as the
server produces. For example, we called read without passing a length
@@ -126,14 +126,14 @@
Low-level connection management
===============================
-When we used open above, we passed an address and a connector, and the
-oprn function created a connection and created file-like objects for
+When we used open above, we passed an address and a connect callable, and the
+open function created a connection and created file-like objects for
output and input. The connect function can be used to create a
connection without a file-like object:
>>> connection = zc.ngi.blocking.connect(('localhost', 42), connector)
-The if the open function is called without a connector, the the first
+The if the open function is called without a connect callable, the the first
object must be a connection object and output and input objects for
that connection will be returned:
Modified: zc.ngi/trunk/src/zc/ngi/interfaces.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/interfaces.py 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/interfaces.py 2009-05-22 22:57:50 UTC (rev 100261)
@@ -14,12 +14,14 @@
"""Network Gateway Interface (NGI)
The interfaces are split between "implementation" and "application"
-interfaces. An implementation of the NGI provides IConnection,
-IConnector, and IListener. An application provides IConnectionHandler
-and one or both of IClientConnectHandler and IServer.
+interfaces. An implementation of the NGI provides IImplementation,
+IConnection, IServerConnection, IServerControl, and IUDPServerControl.
+An TCP application provides IConnectionHandler and one or both of
+IClientConnectHandler and IServer. A UDP server application might
+provide IUDPHandler.
The NGI is an event-based framework in the sense that applications
-register handlers that respond to input events. There are 3 kinds of
+register handlers that respond to input events. There are 4 kinds of
handlers:
- Input handlers receive network input and notification of connection
@@ -29,6 +31,8 @@
- Servers respond to incoming connection events.
+- UDP handlers respond to incoming UDP messages.
+
The interfaces are designed to allow single-threaded applications:
- An implementation of the interfaces is not allowed to make multiple
@@ -54,19 +58,52 @@
from zope.interface import Interface, Attribute
+class IImplementation(Interface):
+ """Standard interface for ngi implementations
+ """
+
+ def connect(address, handler):
+ """Try to make a connection to the given address
+
+ The handler is an IClientConnectHandler. The handler
+ connected method will be called with an IConnection object
+ if and when the connection succeeds or failed_connect method
+ will be called if the connection fails.
+ """
+
+ def listener(address, handler):
+ """Listen for incoming TCP connections
+
+ When a connection is received, call the handler.
+
+ An IListener object is returned.
+ """
+
+ def udp(address, message):
+ """Send a UDP message
+ """
+
+ def udp_listen(address, handler, buffer_size=4096):
+ """Listen for incoming UDP messages
+
+ When a message is received, call the handler with the message.
+
+ An IUDPListener object is returned.
+ """
+
class IConnection(Interface):
"""Network connections
This is an implementation interface.
-
+
Network connections support communication over a network
connection, or any connection having separate input and output
- channels.
+ channels.
"""
def __nonzero__():
"""Return the connection status
-
+
True is returned if the connection is open/active and
False otherwise.
"""
@@ -85,15 +122,15 @@
def write(data):
"""Output a string to the connection.
-
+
The write call is non-blocking.
"""
def writelines(data):
"""Output an iterable of strings to the connection.
-
+
The writelines call is non-blocking. Note, that the data may
- not have been consumed when the method returns.
+ not have been consumed when the method returns.
"""
def close():
@@ -102,10 +139,10 @@
class IServerConnection(IConnection):
"""Server connection
-
+
This is an implementation interface.
"""
-
+
control = Attribute("An IServerControl")
class IConnectionHandler(Interface):
@@ -121,28 +158,28 @@
def handle_input(connection, data):
"""Handle input data from a connection
-
+
The data is an 8-bit string.
Note that there are no promises about blocking. The data
isn't necessarily record oriented. For example, data could,
in theory be passed one character at a time. It is up to
applications to organize data into records, if desired.
-
+
"""
def handle_close(connection, reason):
"""Receive notification that a connection has closed
-
+
The reason argument can be converted to a string for logging
purposes. It may have data useful for debugging, but this
is undefined.
-
+
Notifications are received when the connection is closed
externally, for example, when the other side of the
connection is closed or in case of a network failure. No
notification is given when the connection's close method is
- called.
+ called.
"""
def handle_exception(connection, exception):
@@ -154,21 +191,6 @@
writelines methods.
"""
-class IConnector(Interface):
- """Create a connection to a server
-
- This is an implementation interface.
- """
-
- def __call__(address, handler):
- """Try to make a connection to the given address
-
- The handler is an IClientConnectHandler. The handler
- connected method will be called with an IConnection object
- if and when the connection succeeds or failed_connect method
- will be called if the connection fails.
- """
-
class IClientConnectHandler(Interface):
"""Receive notifications of connection results
@@ -178,7 +200,7 @@
def connected(connection):
"""Receive notification that a connection had been established
"""
-
+
def failed_connect(reason):
"""Receive notification that a connection could not be established
@@ -187,33 +209,31 @@
is undefined.
"""
-class IListener(Interface):
- """Listed for incoming connections
-
- This is an implementation interface.
+class IServer(Interface):
+ """Handle server connections
+
+ This is an application interface.
"""
- def __call__(address, handler):
- """Listen for incoming connections
+ def __call__(connection):
+ """Handle a connection from a client
+ """
- When a connection is received, call the handler.
- An IServerControl object is returned.
- """
+class IUDPHandler(Interface):
+ """Handle udp messages
-class IServer(Interface):
- """Handle server connections
-
This is an application interface.
"""
- def __call__(connection):
+ def __call__(addr, data):
"""Handle a connection from a client
"""
-class IServerControl(Interface):
- """Server information and close control
-
+
+class IListener(Interface):
+ """Listener information and close control
+
This is an implementation interface.
"""
@@ -234,12 +254,22 @@
have been closed.
"""
+class IUDPListener(Interface):
+ """UDP Listener close control
+
+ This is an implementation interface.
+ """
+
+ def close():
+ """Close the listener
+ """
+
class IBlocking(Interface):
"""Top-level blocking interface provided by the blocking module
"""
- def connect(address, connector, timeout=None):
- """Connect to the given address using the given connector
+ def connect(address, connect, timeout=None):
+ """Connect to the given address using the given connect callable
A timout value may be given as a floating point number of
seconds.
@@ -248,14 +278,14 @@
an exception is raised.
"""
- def open(connection_or_address, connector=None, timeout=None):
+ def open(connection_or_address, connect=None, timeout=None):
"""Get output and input files for a connection or address
The first argument is either a connection or an address.
- If (and only if) it is an address, then a connector must be
+ If (and only if) it is an address, then a connect callable must be
provided as the second argument and a connection is gotten by
calling the connect function with the given address,
- connector, and timeout.
+ connect callable, and timeout.
A pair of file-like objects is returned. The first is an
output file-like object, an IBlockingOutput, for sending
@@ -287,7 +317,7 @@
If whence is 2, the position is decreased by the offset.
An exception is raised if the position is set to a negative
- value.
+ value.
"""
def close():
@@ -364,8 +394,6 @@
raised if the data cannot be read in the number of seconds given.
"""
-
-
def __iter__():
"""Return the input object
"""
Modified: zc.ngi/trunk/src/zc/ngi/message.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/message.py 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/message.py 2009-05-22 22:57:50 UTC (rev 100261)
@@ -51,7 +51,7 @@
self.notify(self.input, reason)
-def message(connector, addr, message, expected=None):
+def message(connect, addr, message, expected=None):
result = []
lock = threading.Lock()
lock.acquire()
@@ -60,7 +60,7 @@
return # already notified
result.extend(args)
lock.release()
- connector(addr, Message(message, expected, notify))
+ connect(addr, Message(message, expected, notify))
lock.acquire()
data, reason = result
Modified: zc.ngi/trunk/src/zc/ngi/message.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/message.txt 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/message.txt 2009-05-22 22:57:50 UTC (rev 100261)
@@ -22,20 +22,20 @@
... pass
>>> import zc.ngi.testing
- >>> connector = zc.ngi.testing.peer('foo', EchoServer)
+ >>> connect = zc.ngi.testing.peer('foo', EchoServer)
and we'll use the message client to send it a message and get a
response.
-
+
>>> import zc.ngi.message
>>> import re
>>> expected = re.compile('\n').search
- >>> zc.ngi.message.message(connector, 'foo', 'hello world!\n', expected)
+ >>> zc.ngi.message.message(connect, 'foo', 'hello world!\n', expected)
'HELLO WORLD!\n'
If we give an invalid address, we'll get an exception:
- >>> zc.ngi.message.message(connector, 'bar', 'hello world!\n', expected)
+ >>> zc.ngi.message.message(connect, 'bar', 'hello world!\n', expected)
Traceback (most recent call last):
...
CouldNotConnect: connection refused
Modified: zc.ngi/trunk/src/zc/ngi/testing.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/testing.py 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/testing.py 2009-05-22 22:57:50 UTC (rev 100261)
@@ -157,13 +157,15 @@
_connectable = {}
-def connector(addr, handler):
+def connect(addr, handler):
connections = _connectable.get(addr)
if connections:
handler.connected(connections.pop(0))
else:
handler.failed_connect('no such server')
+connector = connect
+
def connectable(addr, connection):
_connectable.setdefault(addr, []).append(connection)
@@ -174,13 +176,19 @@
self._close_handler = None
self._connections = []
- def connect(self, connection):
+ def connect(self, connection, handler=None):
+ if handler is not None:
+ # connection is addr in this case and is ignored
+ handler.connected(Connection(None, self._handler))
+ return
if self._handler is None:
raise TypeError("Listener closed")
self._connections.append(connection)
connection.control = self
self._handler(connection)
+ connector = connect
+
def connections(self):
return iter(self._connections)
@@ -199,10 +207,6 @@
if not self._connections and self._close_handler:
self._close_handler(self)
- def connector(self, addr, handler):
- handler.connected(Connection(None, self._handler))
-
-
class peer:
def __init__(self, addr, handler):
@@ -237,3 +241,32 @@
finally:
s.close()
raise RuntimeError("Can't find port")
+
+
+class test_udp_handler:
+
+ def __init__(self, addr):
+ self.addr = addr
+
+ def __call__(self, addr, data):
+ sys.stdout.write("udp from %r to %r:\n %r" % (addr, self.addr, data))
+
+_udp_handlers = {}
+class udp_listener:
+
+ def __init__(self, address, handler=None, buffer_size=4096):
+ if handler is None:
+ handler = test_udp_handler(address)
+ self.address = address
+ _udp_handlers[address] = handler, buffer_size
+
+ def close(self):
+ del _udp_handlers[self.address]
+
+def udp(addr, data):
+ handler = _udp_handlers.get(addr)
+ if handler is None:
+ return
+ handler, buffer_size = handler
+ if handler is not None:
+ handler('<test>', data[:buffer_size])
Modified: zc.ngi/trunk/src/zc/ngi/testing.test
===================================================================
--- zc.ngi/trunk/src/zc/ngi/testing.test 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/testing.test 2009-05-22 22:57:50 UTC (rev 100261)
@@ -40,7 +40,7 @@
>>> import zc.ngi.testing
>>> listener = zc.ngi.testing.listener(Greet)
- >>> listener.connector('', Connector())
+ >>> listener.connect('', Connector())
server i got: start
client i got: Hi
server h got: Hi
@@ -55,7 +55,7 @@
... raise ValueError('input', self.state, data)
>>> listener = zc.ngi.testing.listener(Greet2)
- >>> listener.connector('', Connector())
+ >>> listener.connect('', Connector())
... # doctest: +ELLIPSIS
Error test connection calling connection handler:
Traceback (most recent call last):
@@ -65,7 +65,7 @@
server closed i handle_input error
>>> listener = zc.ngi.testing.listener(Greet)
- >>> listener.connector('', Connector(Greet2))
+ >>> listener.connect('', Connector(Greet2))
... # doctest: +ELLIPSIS
server i got: start
Error test connection calling connection handler:
@@ -80,7 +80,7 @@
... raise ValueError('close', self.state, reason)
>>> listener = zc.ngi.testing.listener(Greet2)
- >>> listener.connector('', Connector(Greet3))
+ >>> listener.connect('', Connector(Greet3))
... # doctest: +ELLIPSIS
Error test connection calling connection handler:
Traceback (most recent call last):
Modified: zc.ngi/trunk/src/zc/ngi/tests.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/tests.py 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/tests.py 2009-05-22 22:57:50 UTC (rev 100261)
@@ -36,7 +36,7 @@
... lock.release()
>>> def connect(addr):
- ... zc.ngi.async.connector(addr, Handler())
+ ... zc.ngi.async.connect(addr, Handler())
... lock.acquire()
We find an unused port (so when we connect to it, the connection
@@ -48,13 +48,13 @@
>>> connect(('localhost', port))
failed
-
+
"""
class BrokenConnect:
connected = failed_connect = __call__ = lambda: xxxxx
-
+
class BrokenAfterConnect:
def connected(self, connection):
@@ -71,37 +71,37 @@
# errors are logged.
#import logging
#logging.getLogger().addHandler(logging.StreamHandler())
-
+
# See if we can break the main loop before running the async test
-
+
# Connect to bad port with bad handler
port = zc.ngi.wordcount.get_port()
addr = 'localhost', port
- zc.ngi.async.connector(addr, BrokenConnect())
+ zc.ngi.async.connect(addr, BrokenConnect())
# Start the server and connect to a good port with a bad handler
port = zc.ngi.wordcount.start_server_process(zc.ngi.async.listener)
addr = 'localhost', port
- zc.ngi.async.connector(addr, BrokenAfterConnect())
+ zc.ngi.async.connect(addr, BrokenAfterConnect())
# Stop the server
- zc.ngi.wordcount.stop_server_process(zc.ngi.async.connector, addr)
+ zc.ngi.wordcount.stop_server_process(zc.ngi.async.connect, addr)
# Create a lister with a broken server and connect to it
port = zc.ngi.wordcount.get_port()
addr = 'localhost', port
zc.ngi.async.listener(addr, BrokenConnect())
- zc.ngi.async.connector(addr, BrokenAfterConnect())
+ zc.ngi.async.connect(addr, BrokenAfterConnect())
# Create a lister with a broken Server handler and connect to it
port = zc.ngi.wordcount.get_port()
addr = 'localhost', port
zc.ngi.async.listener(addr, BrokenAfterConnect())
- zc.ngi.async.connector(addr, BrokenAfterConnect())
+ zc.ngi.async.connect(addr, BrokenAfterConnect())
-
+
def test_suite():
return unittest.TestSuite([
doctest.DocFileSuite(
@@ -110,6 +110,7 @@
'message.txt',
'adapters.txt',
'blocking.txt',
+ 'async-udp.test',
),
doctest.DocFileSuite(
'async.txt',
Modified: zc.ngi/trunk/src/zc/ngi/wordcount.py
===================================================================
--- zc.ngi/trunk/src/zc/ngi/wordcount.py 2009-05-22 21:28:26 UTC (rev 100260)
+++ zc.ngi/trunk/src/zc/ngi/wordcount.py 2009-05-22 22:57:50 UTC (rev 100261)
@@ -146,8 +146,8 @@
wait(addr)
return port
-def stop_server_process(connector, addr):
- zc.ngi.message.message(connector, addr, 'Q\0', lambda s: s == 'Q\n')
+def stop_server_process(connect, addr):
+ zc.ngi.message.message(connect, addr, 'Q\0', lambda s: s == 'Q\n')
wait(addr, up=False)
log = open('server.log').read()
os.remove('server.log')
@@ -214,12 +214,12 @@
if self.docs:
print 'unexpected close', reason
-def client_thread(connector, addr):
+def client_thread(connect, addr):
logger.info('client started for %s', addr)
lock = threading.Lock()
lock.acquire()
client = Client(notify=lock.release)
- connector(addr, client)
+ connect(addr, client)
logger.info('client waiting')
lock.acquire() # wait till done
logger.info('client done')
More information about the Checkins
mailing list