<?xml version="1.0" encoding="UTF-8" ?>
<rdf:RDF xmlns="http://usefulinc.com/ns/doap#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><Project><name>zc.ngi</name>
<shortdesc>Network Gateway Interface</shortdesc>
<description>*************************
Network Gateway Interface
*************************

Network programs are typically difficult to test because they require
setting up network connections, clients, and servers.  In addition,
application code gets mixed up with networking code.

The Network Gateway Interface (NGI) seeks to improve this situation by
separating application code from network code.  This allows
application and network code to be tested independently and provides
greater separation of concerns.

.. contents::

Changes
*******

==================
1.1.2 (2009-07-02)
==================

Bugs fixed:

- The zc.ngi.async thread wasn't named. All threads should be named.

==================
1.1.1 (2009-06-29)
==================

Bugs fixed:

- zc.ngi.blocking didn't properly handle connection failures.

==================
1.1.0 (2009-05-26)
==================

Bugs fixed:

- Blocking input and output files didn't properly synchronize closing.

- The testing implementation made muiltiple simultaneous calls to
  handler methods in violation of the promise made in interfaces.py.

- Async TCP servers used too low a listen depth, causing performance
  issues and spurious test failures.

New features:

- Added UDP support.

- Implementation responsibilities were clarified through an
  IImplementation interface.  The "connector" attribute of the testing
  and async implementations was renamed to "connect". The old name
  still works.

- Implementations are now required to log handler errors and to close
  connections in response to connection-handler errors. (Otherwise,
  handlers, and especially handler adapters, would have to do this.)

==================
1.0.1 (2007-05-30)
==================

Bugs fixed:

- Server startups sometimes failed with an error like::

    warning: unhandled read event
    warning: unhandled write event
    warning: unhandled read event
    warning: unhandled write event
    ------
    2007-05-30T22:22:43 ERROR zc.ngi.async.server listener error
    Traceback (most recent call last):
      File "asyncore.py", line 69, in read
        obj.handle_read_event()
      File "asyncore.py", line 385, in handle_read_event
        self.handle_accept()
      File "/zc/ngi/async.py", line 325, in handle_accept
        sock, addr = self.accept()
    TypeError: unpack non-sequence

Detailed Documentation
**********************

=========================
Network Gateway Interface
=========================

Network programs are typically difficult to test because they require
setting up network connections, clients, and servers.  In addition,
application code gets mixed up with networking code.

The Network Gateway Interface (NGI) seeks to improve this situation by
separating application code from network code.  This allows
application and network code to be tested independently and provides
greater separation of concerns.

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 TCP network input.

IClientConnectHandler
    Application callback that handles successful or failed outgoing
    TCP connections.

IServer
    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 Implementation,
IConnection, IListener, and IUDPListener. An application provides
IConnectionHandler and one or more of IClientConnectHandler,
IServer, or IUDPHandler.

For more information, see interfaces.py.

Testing Implementation
======================

These interface can have a number of implementations.  The simplest
implementation is the testing implementation, which is used to test
application code.

    &gt;&gt;&gt; import zc.ngi.testing

The testing module provides IConnection, IConnector, and IListener
implementations. We'll use this below to illustrate how application code
is written.

Implementing Network Clients
============================

Network clients make connections to and then use these connections to
communicate with servers.  To do so, a client must be provided with an
IConnector implantation.  How this happens is outside the scope of
the NGI.  An IConnector implementation could, for example, be provided
via the Zope component architecture, or via pkg_resources entry
points.

Let's create a simple client that calls an echo server and verifies
that the server properly echoes data sent do it.

    &gt;&gt;&gt; class EchoClient:
    ...
    ...     def __init__(self, connect):
    ...         self.connect = connect
    ...
    ...     def check(self, addr, strings):
    ...         self.strings = strings
    ...         self.connect(addr, self)
    ...
    ...     def connected(self, connection):
    ...         for s in self.strings:
    ...             connection.write(s + '\n')
    ...         self.input = ''
    ...         connection.setHandler(self)
    ...
    ...     def failed_connect(self, reason):
    ...         print 'failed connect:', reason
    ...
    ...     def handle_input(self, connection, data):
    ...         print 'got input:', repr(data)
    ...         self.input += data
    ...         while '\n' in self.input:
    ...             data, self.input = self.input.split('\n', 1)
    ...             if self.strings:
    ...                expected = self.strings.pop(0)
    ...                if data == expected:
    ...                    print 'matched:', data
    ...                else:
    ...                    print 'unmatched:', data
    ...                if not self.strings:
    ...                    connection.close()
    ...             else:
    ...                print 'Unexpected input', data
    ...
    ...     def handle_close(self, connection, reason):
    ...         print 'closed:', reason
    ...         if self.strings:
    ...             print 'closed prematurely'
    ...
    ...     def handle_exception(self, connection, exception):
    ...         print 'exception:', exception.__class__.__name__, exception


The client implements the IClientConnectHandler and IConnectionHandler
interfaces.  More complex clients might implement these interfaces with
separate classes.

We'll instantiate our client using the testing connect:

    &gt;&gt;&gt; client = EchoClient(zc.ngi.testing.connect)

Now we'll try to check a non-existent server:

    &gt;&gt;&gt; client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
    failed connect: no such server

Our client simply prints a message (and gives up) if a connection
fails. More complex applications might retry, waiting between attempts,
and so on.

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:

    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; zc.ngi.testing.connectable(('localhost', 42), connection)

We can register multiple connections with the same address:

    &gt;&gt;&gt; connection2 = zc.ngi.testing.Connection()
    &gt;&gt;&gt; zc.ngi.testing.connectable(('localhost', 42), connection2)

The connections will be used in order.

Now, our client should be able to connect to the first connection we
created:

    &gt;&gt;&gt; client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
    -&gt; 'hello\n'
    -&gt; 'world\n'
    -&gt; 'how are you?\n'

The test connection echoes data written to it, preceded by "-&gt; ".

Active connections are true:

    &gt;&gt;&gt; bool(connection2)
    True

Test connections provide methods generating test input and flow closing
connections.  We can use these to simulate network events.  Let's
generate some input for our client:

    &gt;&gt;&gt; connection.test_input('hello')
    got input: 'hello'

    &gt;&gt;&gt; connection.test_input('\nbob\n')
    got input: '\nbob\n'
    matched: hello
    unmatched: bob

    &gt;&gt;&gt; connection.test_close('done')
    closed: done
    closed prematurely

    &gt;&gt;&gt; client.check(('localhost', 42), ['hello'])
    -&gt; 'hello\n'

    &gt;&gt;&gt; connection2.test_input('hello\n')
    got input: 'hello\n'
    matched: hello
    -&gt; CLOSE

    &gt;&gt;&gt; bool(connection2)
    False

Passing iterables to connections
================================

The writelines method of IConnection accepts iterables of strings.

    &gt;&gt;&gt; def greet():
    ...     yield 'hello\n'
    ...     yield 'world\n'

    &gt;&gt;&gt; zc.ngi.testing.Connection().writelines(greet())
    -&gt; 'hello\n'
    -&gt; 'world\n'

If there is an error in your iterator, or if the iterator returns
a non-string value, an exception will be reported using
handle_exception:

    &gt;&gt;&gt; def bad():
    ...     yield 2
    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; connection.setHandler(zc.ngi.testing.PrintingHandler(connection))
    &gt;&gt;&gt; connection.writelines(bad())
    -&gt; EXCEPTION TypeError Got a non-string result from iterable


Implementing network servers
============================

Implementing network servers is very similar to implementing clients,
except that a listener, rather than a connect is used.  Let's
implement a simple echo server:


    &gt;&gt;&gt; class EchoServer:
    ...
    ...     def __init__(self, connection):
    ...         print 'server connected'
    ...         self.input = ''
    ...         connection.setHandler(self)
    ...
    ...     def handle_input(self, connection, data):
    ...         print 'server got input:', repr(data)
    ...         self.input += data
    ...         if '\n' in self.input:
    ...             data, self.input = self.input.split('\n', 1)
    ...             connection.write(data + '\n')
    ...             if data == 'Q':
    ...                 connection.close()
    ...
    ...     def handle_close(self, connection, reason):
    ...         print 'server closed:', reason

Out EchoServer *class* provides IServer and implement IInputHandler.

To use a server, we need a listener.  We'll use the use the testing
listener:

    &gt;&gt;&gt; listener = zc.ngi.testing.listener(EchoServer)

To simulate a client connection, we create a testing connection and
call the listener's connect method:

    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; listener.connect(connection)
    server connected

    &gt;&gt;&gt; connection.test_input('hello\n')
    server got input: 'hello\n'
    -&gt; 'hello\n'

    &gt;&gt;&gt; connection.test_close('done')
    server closed: done

    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; listener.connect(connection)
    server connected

    &gt;&gt;&gt; connection.test_input('hello\n')
    server got input: 'hello\n'
    -&gt; 'hello\n'

    &gt;&gt;&gt; connection.test_input('Q\n')
    server got input: 'Q\n'
    -&gt; 'Q\n'
    -&gt; CLOSE

Note that it is an error to write to a closed connection:

    &gt;&gt;&gt; connection.write('Hello')
    Traceback (most recent call last):
    ...
    TypeError: Connection closed


Server Control
--------------

The object returned from a listener is an IServerControl.  It provides
access to the active connections:

    &gt;&gt;&gt; list(listener.connections())
    []

    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; listener.connect(connection)
    server connected

    &gt;&gt;&gt; list(listener.connections()) == [connection]
    True

    &gt;&gt;&gt; connection2 = zc.ngi.testing.Connection()
    &gt;&gt;&gt; listener.connect(connection2)
    server connected

    &gt;&gt;&gt; len(list(listener.connections()))
    2
    &gt;&gt;&gt; connection in list(listener.connections())
    True
    &gt;&gt;&gt; connection2 in list(listener.connections())
    True

Server connections have a control attribute that is the connections
server control:

    &gt;&gt;&gt; connection.control is listener
    True

Server control objects provide a close method that allows a server to
be shut down.  If the close method is called without arguments, then
then all server connections are closed immediately and no more
connections are accepted:

    &gt;&gt;&gt; listener.close()
    server closed: stopped
    server closed: stopped

    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; listener.connect(connection)
    Traceback (most recent call last):
    ...
    TypeError: Listener closed

If a handler function is passed, then connections aren't closed
immediately:

    &gt;&gt;&gt; listener = zc.ngi.testing.listener(EchoServer)
    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; listener.connect(connection)
    server connected
    &gt;&gt;&gt; connection2 = zc.ngi.testing.Connection()
    &gt;&gt;&gt; listener.connect(connection2)
    server connected

    &gt;&gt;&gt; def handler(control):
    ...     if control is listener:
    ...        print 'All connections closed'

    &gt;&gt;&gt; listener.close(handler)

But no more connections are accepted:

    &gt;&gt;&gt; connection3 = zc.ngi.testing.Connection()
    &gt;&gt;&gt; listener.connect(connection3)
    Traceback (most recent call last):
    ...
    TypeError: Listener closed

And the handler will be called when all of the listener's connections
are closed:

    &gt;&gt;&gt; connection.close()
    -&gt; CLOSE
    &gt;&gt;&gt; connection2.close()
    -&gt; CLOSE
    All connections closed

Long output
===========

Test requests output data written to them.  If output exceeds 50
characters in length, it is wrapped by simply breaking the repr into
50-characters parts:

    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; connection.write('hello ' * 50)
    -&gt; 'hello hello hello hello hello hello hello hello h
    .&gt; ello hello hello hello hello hello hello hello hel
    .&gt; lo hello hello hello hello hello hello hello hello
    .&gt;  hello hello hello hello hello hello hello hello h
    .&gt; ello hello hello hello hello hello hello hello hel
    .&gt; lo hello hello hello hello hello hello hello hello
    .&gt;  '

Text output
===========

If the output from an application consists of short lines of text, a
TextConnection can be used.  A TextConnection simply outputs it's data
directly.

    &gt;&gt;&gt; connection = zc.ngi.testing.TextConnection()
    &gt;&gt;&gt; connection.write('hello\nworld\n')
    hello
    world

END_OF_DATA
===========

Closing a connection closes it immediately, without sending any
pending data.  An alternate way to close a connection is to write
zc.ngi.END_OF_DATA. The connection will be automatically closed when
zc.ngi.END_OF_DATA is encountered in the output stream.

    &gt;&gt;&gt; connection.write(zc.ngi.END_OF_DATA)
    -&gt; CLOSE

    &gt;&gt;&gt; connection.write('Hello')
    Traceback (most recent call last):
    ...
    TypeError: Connection closed

Connecting servers and clients
==============================

It is sometimes useful to connect a client handler and a server
handler.  Listeners created with the zc.ngi.testing.listener class have a
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:

    &gt;&gt;&gt; listener = zc.ngi.testing.listener(EchoServer)

Then we'll use the connect method on the listener:

    &gt;&gt;&gt; client = EchoClient(listener.connect)
    &gt;&gt;&gt; client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
    server connected
    server got input: 'hello\n'
    server got input: 'world\n'
    server got input: 'how are you?\n'
    got input: 'hello\nworld\nhow are you?\n'
    matched: hello
    matched: world
    matched: how are you?
    server closed: closed

.. Peer connectors

  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
  connection to a peer handler. To illustrate, we'll set up an echo
  client that connects to our echo server:

    &gt;&gt;&gt; client = EchoClient(zc.ngi.testing.peer(('localhost', 42), EchoServer))
    &gt;&gt;&gt; client.check(('localhost', 42), ['hello', 'world', 'how are you?'])
    server connected
    server got input: 'hello\n'
    server got input: 'world\n'
    server got input: 'how are you?\n'
    got input: 'hello\nworld\nhow are you?\n'
    matched: hello
    matched: world
    matched: how are you?
    server closed: closed

UDP Support
===========

To send a UDP message, just use an implementations udp method:

    &gt;&gt;&gt; zc.ngi.testing.udp(('', 42), "hello")

If there isn't a server listening, the call will effectively be
ignored. This is UDP. :)

    &gt;&gt;&gt; def my_udp_handler(addr, data):
    ...     print 'from %r got %r' % (addr, data)

    &gt;&gt;&gt; listener = zc.ngi.testing.udp_listener(('', 42), my_udp_handler)

    &gt;&gt;&gt; zc.ngi.testing.udp(('', 42), "hello")
    from '&lt;test&gt;' got 'hello'

    &gt;&gt;&gt; listener.close()
    &gt;&gt;&gt; zc.ngi.testing.udp(('', 42), "hello")

A default handler is used if you don't pass a handler:

    &gt;&gt;&gt; listener = zc.ngi.testing.udp_listener(('', 43))
    &gt;&gt;&gt; zc.ngi.testing.udp(('', 43), "hello")
    udp from '&lt;test&gt;' to ('', 43):
      'hello'

    &gt;&gt;&gt; listener.close()
    &gt;&gt;&gt; zc.ngi.testing.udp(('', 43), "hello")

=======================
Blocking network access
=======================

The NGI normally uses an event-based networking model in which
application code reactes to incoming data.  That model works well for
some applications, especially server applications, but can be a bit of
a bother for simpler applications, especially client applications.

The zc.ngi.blocking module provides a simple blocking network model.
The open function can be used to create a pair of file-like objects
that can be used for writing output and reading input.  To illustrate
this, we'll use the wordcount server.  We'll use the peer function to
create a testing connector that connects to the server directory
without using a network:

    &gt;&gt;&gt; import zc.ngi.wordcount
    &gt;&gt;&gt; import zc.ngi.testing
    &gt;&gt;&gt; connector = zc.ngi.testing.peer(('localhost', 42),
    ...                                 zc.ngi.wordcount.Server)

The open function is called with an address and a connect callable:

    &gt;&gt;&gt; import zc.ngi.blocking
    &gt;&gt;&gt; output, input = zc.ngi.blocking.open(('localhost', 42), connector)

The output file lets us send output to the server:

    &gt;&gt;&gt; output.write("Hello\n")
    &gt;&gt;&gt; output.write("world\n")
    &gt;&gt;&gt; output.write("\0")

The wordcount server accepts a sequence of text from the
client. Delimited by null characters.  For each input text, it
generates a line of summary statistics:

    &gt;&gt;&gt; input.readline()
    '2 2 12\n'

We can use the writelines method to send data using an iterator:

    &gt;&gt;&gt; def hello(name):
    ...     yield "hello\n"
    ...     yield name
    ...     yield "\0"

    &gt;&gt;&gt; output.writelines(hello("everyone"))
    &gt;&gt;&gt; output.writelines(hello("bob"))

 To close the connection to the server, we'll send a close command,
 which is a documenty consisting of the letter "C":

    &gt;&gt;&gt; output.write("C\0")

This causes the server to close the connection after it has sent it's
data.

We can use the read function to read either a fixed number of bytes
from the server:

    &gt;&gt;&gt; input.read(5)
    '1 2 1'

Or to read the remaining data:

    &gt;&gt;&gt; input.read()
    '4\n1 2 9\n'

If read is called without a size, it won't return until the server has
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
only after sending a quit command to the server.  When using the
blocking library, care is needed to avoid a deadlock, in which both
sides of a connection are waiting for input.

The blocking open and input methods accept an optional timeout
argument.  The timeout argument accepts a floating-point time-out
value, in seconds.  If a connection or input operation times out, a
Timeout exception is raised:

    &gt;&gt;&gt; output, input = zc.ngi.blocking.open(('localhost', 42), connector)
    &gt;&gt;&gt; import time
    &gt;&gt;&gt; then = time.time()
    &gt;&gt;&gt; input.read(5, timeout=0.5)
    Traceback (most recent call last):
    ...
    Timeout

    &gt;&gt;&gt; 0.5 &lt;= (time.time() - then) &lt; 1
    True

The readline and readlines functions accept a timeout as well:

    &gt;&gt;&gt; then = time.time()
    &gt;&gt;&gt; input.readline(timeout=0.5)
    Traceback (most recent call last):
    ...
    Timeout

    &gt;&gt;&gt; 0.5 &lt;= (time.time() - then) &lt; 1
    True

    &gt;&gt;&gt; then = time.time()
    &gt;&gt;&gt; input.readlines(timeout=0.5)
    Traceback (most recent call last):
    ...
    Timeout

    &gt;&gt;&gt; 0.5 &lt;= (time.time() - then) &lt; 1
    True

Timeouts can also be specified when connecting. To illustrate this,
we'll pass a do-nothing connector:

    &gt;&gt;&gt; then = time.time()
    &gt;&gt;&gt; zc.ngi.blocking.open(None, (lambda *args: None), timeout=0.5)
    Traceback (most recent call last):
    ...
    ConnectionTimeout

    &gt;&gt;&gt; 0.5 &lt;= (time.time() - then) &lt; 1
    True

Low-level connection management
===============================

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:

    &gt;&gt;&gt; connection = zc.ngi.blocking.connect(('localhost', 42), connector)

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:

    &gt;&gt;&gt; output, input = zc.ngi.blocking.open(connection)
    &gt;&gt;&gt; output.write("Hello\n")
    &gt;&gt;&gt; output.write("world\n")
    &gt;&gt;&gt; output.write("\0")
    &gt;&gt;&gt; input.readline()
    '2 2 12\n'

Like the open function, the connect function accepts a timeout:

    &gt;&gt;&gt; then = time.time()
    &gt;&gt;&gt; zc.ngi.blocking.connect(None, (lambda *args: None), timeout=0.5)
    Traceback (most recent call last):
    ...
    ConnectionTimeout

    &gt;&gt;&gt; 0.5 &lt;= (time.time() - then) &lt; 1
    True

============
NGI Adapters
============

The NGI is a fairly low-level event-based framework.  Adapters can be
used to build higher-level semantics.  In this document, we'll describe
some sample adapters that provide more examples of using the NGI and
useful building blocks for other applications. The source for these
adapters can be found in the zc.ngi.adapters module.

Lines
=====

The first adapter we'll look at collects input into lines. To
illustrate this, we'll use a handler from zc.ngi.testing that simply
prints its input:

    &gt;&gt;&gt; import zc.ngi.testing
    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; handler = zc.ngi.testing.PrintingHandler(connection)
 
This handler is used by default as the peer
of testing connections:

    &gt;&gt;&gt; connection.test_input('x' * 80)
    -&gt; 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    .&gt; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

    &gt;&gt;&gt; connection.test_close('test')
    -&gt; CLOSE test

Now, we'll use the lines adapter to break input into lines, separated
by newlines.  We apply the adapter to a connection:

    &gt;&gt;&gt; import zc.ngi.adapters
    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; adapter = zc.ngi.adapters.Lines(connection)
    &gt;&gt;&gt; handler = zc.ngi.testing.PrintingHandler(adapter)

Now, when we provide input, it won't appear until lines are complete:

    &gt;&gt;&gt; connection.test_input('Hello world!')
    &gt;&gt;&gt; connection.test_input('\n')
    -&gt; 'Hello world!'

    &gt;&gt;&gt; connection.test_input('Hello\nWorld!\nHow are you')
    -&gt; 'Hello'
    -&gt; 'World!'

Only input handling is affected.  Other methods of the adapter behave
as would the underlying connection:

    &gt;&gt;&gt; adapter.write('foo')
    -&gt; 'foo'

    &gt;&gt;&gt; connection.test_close('test')
    -&gt; CLOSE test

The original connection is available in the connection attribute:

    &gt;&gt;&gt; adapter.connection is connection
    True

Sized Messages
==============

The second adapter we'll look at will handle binary data organized
into sized messages.  Each message has two parts, a length, and a
payload.  Of course, the length gives the length of the payload.

To see this, we'll use the adapter to adapt a testing connection:

    &gt;&gt;&gt; connection = zc.ngi.testing.Connection()
    &gt;&gt;&gt; adapter = zc.ngi.adapters.Sized(connection)
    &gt;&gt;&gt; handler = zc.ngi.testing.PrintingHandler(adapter)

Now, we'll generate some input. We do so by providing (big-endian) sizes by
calling struct pack:
    
    &gt;&gt;&gt; import struct
    &gt;&gt;&gt; message1 = 'Hello\nWorld!\nHow are you?'
    &gt;&gt;&gt; message2 = 'This is message 2'
    &gt;&gt;&gt; connection.test_input(struct.pack("&gt;I", len(message1)))
    &gt;&gt;&gt; connection.test_input(message1[:10])
    &gt;&gt;&gt; connection.test_input(message1[10:]+ struct.pack("&gt;I", len(message2)))
    -&gt; 'Hello\nWorld!\nHow are you?'

    &gt;&gt;&gt; connection.test_input(message2)
    -&gt; 'This is message 2'

Here we saw that our handler got the two messages individually.

If we write a message, we can see that the message is preceded by the
message size:

    &gt;&gt;&gt; adapter.write(message1) 
    -&gt; '\x00\x00\x00\x19'
    -&gt; 'Hello\nWorld!\nHow are you?'
   
Null messages
-------------

It can be useful to send Null messages to make sure that a client is
still connected.  The sized adapter supports such messages.  Calling
write with None, sends a null message, which is a message with a
length of 1 &lt;&lt; 32 - 1 and no message data:

    &gt;&gt;&gt; adapter.write(None)
    -&gt; '\xff\xff\xff\xff'

On input, Null messages are ignored by the sized adapter and are not
sent to the application:

    &gt;&gt;&gt; connection.test_input('\xff\xff\xff\xff')


 




=================================
asyncore-based NGI implementation
=================================

The async module provides an NGI implementation based on the Python
standard asyncore framework.  It provides 2 objects to be invoked
directly by applications:

connector
   an implementation of the NGI IConnector interface

listener
   an implementation of the NGI IListener interface

The implementation creates a dedicated thread to run an asyncore main
loop on import.

There's nothing else to say about the implementation from a usage
point of view.  The remainder of this document provides a
demonstration (test) of using the impemantation to create a simple
word-count server and client.

Demonstration: wordcount
========================

The wordcount module has a simple word-count server and client
implementation.  We'll run these using the async implementation.

Let's start the wordcount server:

    &gt;&gt;&gt; import zc.ngi.wordcount
    &gt;&gt;&gt; import zc.ngi.async
    &gt;&gt;&gt; port = zc.ngi.wordcount.start_server_process(zc.ngi.async.listener)

We passed the listener to be used.

Now, we'll start a number of threads that connect to the server and
check word counts of some sample documents.  If all goes well, we
shouldn't get any output.

    &gt;&gt;&gt; import threading
    &gt;&gt;&gt; addr = 'localhost', port
    &gt;&gt;&gt; threads = [threading.Thread(target=zc.ngi.wordcount.client_thread,
    ...                             args=(zc.ngi.async.connect, addr))
    ...            for i in range(200)]

    &gt;&gt;&gt; _ = [thread.start() for thread in threads]
    &gt;&gt;&gt; _ = [thread.join() for thread in threads]

Iterable input
==============

We can pass data to the server using an iterator.  To illustrate this,
we'll use the blocking interface:

    &gt;&gt;&gt; import zc.ngi.blocking
    &gt;&gt;&gt; output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connect,
    ...                                      timeout=1.0)
    &gt;&gt;&gt; def hello(name):
    ...     yield "hello\n"
    ...     yield name
    ...     yield "\0"

    &gt;&gt;&gt; output.writelines(hello('world'), timeout=1.0)
    &gt;&gt;&gt; input.readline(timeout=1.0)
    '1 2 11\n'

.. Error handling:

   If we pass a non-iterable to writelines, we'll get an immediate
   error.  To demonstrate this we'll violate our output file and
   access it's _connection attribute so that we can bypass the check
   in the blocking writelines method:

    &gt;&gt;&gt; output._connection.writelines(2) # doctest: +ELLIPSIS
    Traceback (most recent call last):
    ...
    TypeError: ...

    &gt;&gt;&gt; output._connection.writelines('foo')
    Traceback (most recent call last):
    ...
    AssertionError: writelines does not accept strings

   If we pass an iterable that returns a non-string, we'll get a type
   error when we try to read because handle_exception is called in
   the input handler.

    &gt;&gt;&gt; output.writelines([2], timeout=0.1)
    Traceback (most recent call last):
    ...
    Timeout

    &gt;&gt;&gt; input.readline()
    Traceback (most recent call last):
    ...
    TypeError: ('iterable output returned a non-string', 2)

  If there is an error, then the connection is closed:

    &gt;&gt;&gt; input.read()
    ''

    &gt;&gt;&gt; output.write('hello')
    Traceback (most recent call last):
    ...
    IOError: I/O operation on closed file

  Handler errors cause connections to be closed.  To see this, we'll
  send the server an error message, which foreces an error:

    &gt;&gt;&gt; output, input = zc.ngi.blocking.open(addr, zc.ngi.async.connect,
    ...                                      timeout=1.0)
    &gt;&gt;&gt; output.write('E\0')
    &gt;&gt;&gt; input.read()
    ''

  Let's create some lame clients:

    &gt;&gt;&gt; import zope.testing.loggingsupport, logging
    &gt;&gt;&gt; loghandler = zope.testing.loggingsupport.InstalledHandler(
    ...                  None, level=logging.ERROR)

    &gt;&gt;&gt; event = threading.Event()
    &gt;&gt;&gt; class LameClientConnectionHandler:
    ...     def connected(self, connection):
    ...         connection.setHandler(self)
    ...         raise ValueError('Broken connector')
    ...     def handle_close(self, conection, reason):
    ...         self.closed = reason
    ...         event.set()

    &gt;&gt;&gt; handler = LameClientConnectionHandler()
    &gt;&gt;&gt; zc.ngi.async.connect(addr, handler)
    &gt;&gt;&gt; event.wait(1)

    &gt;&gt;&gt; print loghandler
    zc.ngi.async.client ERROR
      connection handler failed

    &gt;&gt;&gt; handler.closed
    'connection handler failed'


    &gt;&gt;&gt; loghandler.clear()
    &gt;&gt;&gt; event.clear()

    &gt;&gt;&gt; class LameClientConnectionHandler:
    ...     def connected(self, connection):
    ...         connection.setHandler(self)
    ...         connection.write('foo\0')
    ...
    ...     def handle_input(self, data):
    ...         raise ValueError()
    ...
    ...     def handle_close(self, conection, reason):
    ...         self.closed = reason
    ...         event.set()

    &gt;&gt;&gt; handler = LameClientConnectionHandler()
    &gt;&gt;&gt; zc.ngi.async.connect(addr, handler)
    &gt;&gt;&gt; event.wait(1)

    &gt;&gt;&gt; print loghandler
    zc.ngi.async.client ERROR
      handle_input failed

    &gt;&gt;&gt; handler.closed
    'handle_input failed'

    &gt;&gt;&gt; loghandler.uninstall()


.. stop the server

    &gt;&gt;&gt; zc.ngi.wordcount.stop_server_process(zc.ngi.async.connect, addr)
    ... # doctest: +ELLIPSIS
    handle_input failed
    Traceback (most recent call last):
    ...
    ValueError: E

   The server log was printed. Note that we see the Error that we
   requested above.

Download
**********************</description>
<homepage rdf:resource="http://www.python.org/pypi/zc.ngi" />
<maintainer><foaf:Person><foaf:name>Jim Fulton</foaf:name>
<foaf:mbox_sha1sum>0491e393764bbbfa13324a845db4ee27f7641b78</foaf:mbox_sha1sum></foaf:Person></maintainer>
<release><Version><revision>1.1.2</revision></Version></release>
</Project></rdf:RDF>