protocol.py   [plain text]


# Twisted, the Framework of Your Internet
# Copyright (C) 2003 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General
# Public License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
# USA
#
# Author: Clark Evans  (cce@clarkevans.com)
#

""" flow.protocol

    This allows one to use flow module to create protocols, a 
    protocol is actually a controller, but it is specialized 
    enough to deserve its own module.
"""

import types
from base import *
from wrap import wrap
from stage import Callback
from twisted.internet import protocol
from twisted.internet.error import ConnectionLost, ConnectionDone

def makeProtocol(controller, baseClass = protocol.Protocol, 
                  *callbacks, **kwargs):
    """ Construct a flow based protocol

        This takes a base protocol class, and a set of callbacks and
        creates a connection flow based on the two.   For example, 
        the following would build a simple 'echo' protocol.

            from __future__ import generators
            from twisted.internet import reactor, protocol
            from twisted.flow import flow
            PORT = 8392

            def echoServer(conn):
                yield conn
                for data in conn:
                    conn.write(data)
                    yield conn
            
            def echoClient(conn):
                conn.write("hello, world!")
                yield conn
                print "server said: ", conn.next()
                reactor.callLater(0,reactor.stop)
            
            server = protocol.ServerFactory()
            server.protocol = flow.makeProtocol(echoServer)
            reactor.listenTCP(PORT,server)
            client = protocol.ClientFactory()
            client.protocol = flow.makeProtocol(echoClient)
            reactor.connectTCP("localhost", PORT, client)
            reactor.run()

        Of course, the best part about flow is that you can nest
        stages.  Therefore it is quite easy to make a lineBreaker
        generator which takes an input connection and produces
        and output connection.   Anyway, the code is almost 
        identical as far as the client/server is concerned:

            # this is a filter generator, it consumes from the
            # incoming connection, and yields results to
            # the next stage, the echoServer below
            def lineBreaker(conn, lineEnding = "\n"):
                lst = []
                yield conn
                for chunk in conn:
                   pos = chunk.find(lineEnding)
                   if pos > -1:
                       lst.append(chunk[:pos])
                       yield "".join(lst)
                       lst = [chunk[pos+1:]]
                   else:
                       lst.append(chunk)
                   yield conn
                yield "".join(lst)

            # note that this class is only slightly modified,
            # simply comment out the line breaker line to see
            # how the server behaves without the filter...
            def echoServer(conn):
                lines = flow.wrap(lineBreaker(conn))
                yield lines
                for data in lines:
                    conn.write(data)
                    yield lines
            
            # and the only thing that is changed is that we
            # are sending data in strange chunks, and even
            # putting the last chunk on hold for 2 seconds. 
            def echoClient(conn):
                conn.write("Good Morning!\nPlease ")
                yield conn
                print "server said: ", conn.next()
                conn.write("do not disregard ")
                reactor.callLater(2, conn.write, "this.\n")
                yield conn
                print "server said: ", conn.next()
                reactor.callLater(0,reactor.stop)

    """
    if not callbacks:
        callbacks = ('dataReceived',)
    trap = kwargs.get("trap", tuple())
    class _Protocol(Controller, Callback, baseClass):
        def __init__(self):
            Callback.__init__(self, *trap)
            setattr(self, callbacks[0], self)  
            # TODO: support more than one callback via Concurrent
        def _execute(self, dummy = None):
            cmd = self._controller
            self.write = self.transport.write
            while True:
                instruction = cmd._yield()
                if instruction:
                    if isinstance(instruction, CallLater):
                        instruction.callLater(self._execute)
                        return
                    raise Unsupported(instruction)
                if cmd.stop:
                    self.transport.loseConnection()
                    return
                if cmd.failure:
                    self.transport.loseConnection()
                    cmd.failure.trap()
                    return
                if cmd.results:
                    self.transport.writeSequence(cmd.results)
                    cmd.results = []
        def connectionMade(self):
            if types.ClassType == type(self.controller):
                self._controller = wrap(self.controller(self))
            else:
                self._controller = wrap(self.controller())
            self._execute()
        def connectionLost(self, reason=protocol.connectionDone):
            if isinstance(reason.value, ConnectionDone) or \
               (isinstance(reason.value, ConnectionLost) and \
                self.finishOnConnectionLost):
                self.finish()
            else:
                self.errback(reason)
            self._execute()
    _Protocol.finishOnConnectionLost = kwargs.get("finishOnConnectionLost",True)
    _Protocol.controller = controller
    return _Protocol

def _NotImplController(protocol):
    raise NotImplementedError
Protocol = makeProtocol(_NotImplController) 
Protocol.__doc__ = """ A concrete flow.Protocol for inheritance """