# 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 """
syntax highlighted by Code2HTML, v. 0.9.1