[Checkins] SVN: zc.ngi/trunk/src/zc/ngi/doc/index.txt Refinements
Jim Fulton
jim at zope.com
Thu Jul 8 07:12:57 EDT 2010
Log message for revision 114323:
Refinements
Changed:
U zc.ngi/trunk/src/zc/ngi/doc/index.txt
-=-
Modified: zc.ngi/trunk/src/zc/ngi/doc/index.txt
===================================================================
--- zc.ngi/trunk/src/zc/ngi/doc/index.txt 2010-07-08 09:05:48 UTC (rev 114322)
+++ zc.ngi/trunk/src/zc/ngi/doc/index.txt 2010-07-08 11:12:56 UTC (rev 114323)
@@ -95,26 +95,28 @@
handlers will call the ``write``, ``writelines`` [#writelines]_, or
``close`` methods from the handler's ``handle_input`` method.
-The handler's ``handle_close`` and ``handle_exception`` methods are optional.
-The ``handle_exception`` method is only called if an iterator created from
-an iterable passed to ``writelines`` raises an exception. If a call to
-``handle_exception`` fails, an implementation will close the connection.
+The handler's ``handle_close`` and ``handle_exception`` methods are
+optional. The ``handle_exception`` method is only called if an
+iterator created from an iterable passed to ``writelines`` raises an
+exception. If a call to ``handle_exception`` fails, or if
+``handle_exception`` isn't implemented, an implementation will close
+the connection and call ``handle_close`` (if it is implemented).
-The ``handle_close`` method is called when a connection is closed other
-than through the connection handler calling the connection's ``close``
-method. For many applications, this is uninteresting, which is why
-the method is optional. Clients that maintain long-running
+The ``handle_close`` method is called when a connection is closed
+other than through the connection handler calling the connection's
+``close`` method. For many applications, this is uninteresting, which
+is why the method is optional. Clients that maintain long-running
connections, may try to create new connections when notified that a
connection has closed.
Testing connection handlers
---------------------------
-Testing a connection handler is very easy. Just call its methods
-passing suitable arguments. The ``zc.ngi.testing`` module provides a
+Testing a connection handler is easy. Just call its methods passing
+suitable arguments. The ``zc.ngi.testing`` module provides a
connection implementation designed to make testing convenient. For
-example, to test our ``Echo`` connection handler, we can use code like the
-following::
+example, to test our ``Echo`` connection handler, we can use code like
+the following::
>>> import zc.ngi.testing
>>> connection = zc.ngi.testing.Connection()
@@ -122,7 +124,7 @@
>>> handler.handle_input(connection, 'hello out there')
-> 'HELLO OUT THERE'
-Any data written to the connection, using its ``write`` or ``writelines``
+Any data written to the test connection, using its ``write`` or ``writelines``
methods, is written to standard output preceded by "-> "::
>>> handler.handle_close(connection, 'done')
@@ -133,11 +135,12 @@
Implementing servers is only slightly more involved that implementing
connection handlers. A server is just a callable that takes a
-connection and gives it a handler. For example, we can use a simple
-function to implement a server for the Echo handler::
+connection and gives it a handler by calling ``set_hsndler``. For
+example, we can use a simple function to implement a server for the
+Echo handler::
def echo_server(connection):
- connection.setHandler(Echo())
+ connection.set_handler(Echo())
.. -> src
@@ -193,7 +196,7 @@
about these objects in a little bit. We then call the
``zc.ngi.async.main.loop`` method, which blocks until either:
-- A handler raises an exception, or
+- a handler raises an exception, or
- there are no active handlers.
@@ -207,12 +210,12 @@
--------------------------------------------------
It's often simplest to implement a server using a connection handler
-class that takes a connection in it's constructor:
+class that takes a connection in it's constructor::
class EchoServer:
def __init__(self, connection):
- connection.setHandler(self)
+ connection.set_handler(self)
def handle_input(self, connection, data):
connection.write(data.upper())
@@ -236,58 +239,21 @@
Remember a server is just a callable that takes a connection and
sets its handler.
-Testing servers
----------------
+Testing listeners
+-----------------
-When testing servers, we'll often use the
-``zc.ngi.testing.listener`` function::
+The testing implementation provides a ``listener`` function::
- >>> listener = zc.ngi.testing.listener(address, EchoServer)
+ >>> listener = zc.ngi.testing.listener('addr', EchoServer)
-Generally, the address will either be a host/port tuple or the name of
-a Unix domain socket, although an implementation may define a custom
-address representation. The ``zc.ngi.testing.listener`` function will
-take any hashable address object.
+This is primarily useful when you want to connect client and server
+handlers, as we'll discuss later. The address passed to the
+testing listener function can be any hashable object.
-We can connect to a *testing* listener using its connect method::
+Creating a testing listener causes it to be registered in a mapping
+so it can be connected to later. For this reason, it's important to
+close any listeners created in tests.
- >>> connection = listener.connect()
-
-The connection returned from ``listener.connect`` is not the connection
-passed to the server. Instead, it's a test connection that we can use
-as if we're writing a client::
-
- >>> connection.write('Hi\nthere.')
- -> 'HI\nTHERE.'
-
-It is actually a peer of the connection passed to the server. Testing
-connections have peer attributes that you can use to get to the peer
-connection::
-
- >>> connection.peer.peer is connection
- True
- >>> list(listener.connections()) == [connection.peer]
- True
-
-The test connection has a default handler that just prints data to
-standard output, but we can call ``setHandler`` on it to use a different
-handler::
-
- >>> class Handler:
- ... def handle_input(self, connection, data):
- ... print 'got', `data`
- >>> connection.setHandler(Handler())
- >>> connection.write('take this')
- got 'TAKE THIS'
-
-Now, the data sent back from the server is handled by our custom
-handler, rather than the default one.
-
-.. cleanup
-
- >>> listener.close()
- closed stopped
-
Listener objects
----------------
@@ -302,14 +268,12 @@
>>> listener.close()
-
.. XXX Future
There's also a ``close_wait`` method that stops listening and waits
for a given period of time for clients to finish on their own before
closing them.
-
Threading
=========
@@ -324,7 +288,7 @@
``close`` are thread safe. They may be called at
any time by any thread.
- The connection setHandler method must only be called in a connect
+ The connection set_handler method must only be called in a connect
handler's ``connected`` method or a connection handler's
``handle_input`` method.
@@ -336,6 +300,10 @@
implementations will never call them from more than one thread at a
time.
+- Handlers block implementations When an implementatuon calls a
+ handler, it is blocked from handling other network events until the
+ handler returns.
+
``zc.ngi.async`` implementations and threading
----------------------------------------------
@@ -385,14 +353,18 @@
are no handlers registered with the implementation.
``zc.ngi.async.main`` is a ``zc.ngi.async.Inline`` instance.
+An advantage of the application-managed loop options is that
+exceptions raised by handlers are propagated to the application. When
+an implementation manages a loop thread, it logs exceptions.
+
Performance issues with a single loop
-------------------------------------
With a single loop, all networking activity is done in one thread.
-If a handler takes a long time to perform some function, it can
-prevent other networking activity from proceeding. For this reason,
+If a handler takes a long time to perform some function, it
+prevents other networking activity from proceeding. For this reason,
when a single loop is used, it's important that handlers perform their
-work quickly, without blocking for any length of time.
+work quickly, without blocking for any significant length of time.
If a loop is only servicing a single handler, or a small number of
handlers, it's not a problem if a handler takes along time to respond
@@ -538,7 +510,7 @@
====================
Implementing clients is a little bit more involved than implementing
-servers because in addition to handling connections, you have to
+servers because, in addition to handling connections, you have to
initiate the connections in the first place. This involves
implementing client connect handlers. You request a connection by
calling an implementation's ``connect`` function, passing an address
@@ -555,7 +527,7 @@
self.data = data
def connected(self, connection):
- connection.setHandler(LineReader())
+ connection.set_handler(LineReader())
connection.write(self.data)
def failed_connect(self, reason):
@@ -601,6 +573,18 @@
<BLANKLINE>
-> CLOSE
+Testing connections are always created in pairs. Each connection in
+the pair is the other's peer::
+
+ >>> connection.peer.peer is connection
+ True
+
+When a connection is created directly, it's peer has a simple printing
+handler, which is why, when we write to the connection, the text we
+write is written out with a marker. When we create a connection by
+connecting to a listener, the connections's peer's handler is the
+server used to create the listener.
+
Combining connect handlers with connection handlers
---------------------------------------------------
@@ -612,7 +596,7 @@
self.data = data
def connected(self, connection):
- connection.setHandler(self)
+ connection.set_handler(self)
connection.write("%s\n%s" % (len(self.data), self.data))
def failed_connect(self, reason):
@@ -764,7 +748,7 @@
self.connector(self.address, self)
def connected(self, connection):
- connection.setHandler(self)
+ connection.set_handler(self)
def failed_connect(self, reason):
print 'failed connect', reason
@@ -781,7 +765,7 @@
>>> exec(src)
-To try this out, we'll create a trivial connector that just remembers
+To try this out, we'll create a trivial connector that just notes
the attempt::
def connector(addr, handler):
@@ -865,13 +849,13 @@
self.write = connection.write
self.writelines = connection.writelines
- def setHandler(self, handler):
+ def set_handler(self, handler):
self.handler = handler
if hasattr(handler, 'handle_close'):
self.handle_close = handler.handle_close
if hasattr(handler, 'handle_exception'):
self.handle_exception = handler.handle_exception
- self.connection.setHandler(self)
+ self.connection.set_handler(self)
def handle_input(self, connection, data):
self.input += data
@@ -891,18 +875,18 @@
>>> exec(src)
With this adapter, we can now write a much simpler version of the
-word-count server:
+word-count server::
class WCAdapted:
def __init__(self, connection):
- Sized(connection).setHandler(self)
+ Sized(connection).set_handler(self)
def handle_input(self, connection, data):
connection.write(
- '%d %d\n' % (len(data.split('\n')), len(data.split())))
+ '%d %d\n' % (len(data.split('\n')),
+ len(data.split())))
-
.. -> src
>>> exec(src)
@@ -925,7 +909,8 @@
while 1:
data = (yield)
connection.write(
- '%d %d\n' % (len(data.split('\n')), len(data.split())))
+ '%d %d\n' % (len(data.split('\n')),
+ len(data.split())))
.. -> src
More information about the checkins
mailing list