[Twisted-Python] stdio.StandardIO, ServerProtocol and Services.

kgi iacovou at gmail.com
Fri Oct 5 14:45:13 EDT 2007


Hi all.

I'm trying to write a "terminal" component, wrapped in a
service.Service, to drop into an application framework so that I can
use it to build interactive command-line tools. I've almost, but not
quite, got it working.

I know about manhole, but I want to write a simple command-based
syntax, rather than exposing a python namespace and interpreter. I
could also just go with  a telnet or ssh interface, but that would
require an additional step (the actual telnet or ssh). There's also
stdiodemo.py, but that doesn't offer readline-like functionality,
which would be a Very Good Thing.

(Besides, I'm really curious as to why this approach is not working!)

The code below is a minimal stripped-down example of what I've got so
far through frantic grepping and glimpseing through the Twisted
codebase. (although there are some fragmentary holdovers from various
experiments; ignore these). At the moment, the command handler (the
lineReceived() method) just echoes the argument with some '+' signs
prepended.

The termios stuff comes from twisted/conch/stdio.py, via a mailing
list post saying something to the effect of, "this is the minimal
amount you have to do in order to hook up your terminal to your
process's stdio"; I don't have the ML link handy right now, sorry.

Note that whereas stdio.py passes a Protocol argument to
ServerProtocol, I've created an intermediate class, CLIServerProtocol,
which has a protocolFactory attribute. An examination of
twisted/conch/insults/insults.py shows that this should be equivalent.

For the curious bystanders, HistoricRecvLine is derived as follows:

  HistoricRecvLine -> RecvLine -> TerminalProtocol

... and TerminalProtocol implements things like connectionMade, etc.

Here's the behaviour I see:

With the code as is, I can type in lines and get a reply from
lineReceived. However, none of the cursor keys or other things for
which there is special code in twisted/conch/recvline.py work; I just
get control codes all over the terminal.

If I uncomment the "reactor.run()", however, the behaviour changes
completely, and becomes much closer to what I expect: keypresses like
Home, cursor keys, etc, are honoured, as well as the history. However,
Ctrl-C merely loses the connection, and the terminal hangs and I have
to kill the twistd process externally.

The fact that adding reactor.run() (which shouldn't be needed in a
Service, right?) implies to me that the reactor isn't starting up
properly without it, and I can't see why.

Any advice would be greatly appreciated.

Ricky

--

import os, tty, sys, termios

from twisted.application import service

from twisted.internet import reactor, stdio, protocol, defer
from twisted.python import failure, reflect, log
from twisted.protocols import basic
from twisted.application import internet

from twisted.conch.insults import insults
from twisted.conch.manhole import ColoredManhole
# from twisted.conch.stdio import ConsoleManhole

from twisted.application                import service
from twisted.conch import recvline


class CLIProtocol ( recvline.HistoricRecvLine ):

    service = None

    def connectionMade ( self ):
        recvline.HistoricRecvLine.connectionMade ( self )
        self.keyHandlers [ '\x01' ] = self.handle_HOME
        self.keyHandlers [ '\x03' ] = self.handle_QUIT
        self.keyHandlers [ '\x1a' ] = self.handle_QUIT


    def connectionLost ( self, reason ):
        log.msg ( "Connection Lost" )


    def handle_QUIT ( self ):
        self.terminal.loseConnection()

    def lineReceived ( self, line ):
        self.terminal.write ( '+++' + line )


class CLIServerProtocol ( insults.ServerProtocol ):
    protocolFactory = CLIProtocol


class CLIService ( service.Service ):

    def startService ( self ):
        fd = sys.__stdin__.fileno()
        oldSettings = termios.tcgetattr ( fd )
        tty.setraw ( fd )
        try:
            p = CLIServerProtocol()
            stdio.StandardIO ( p )
            # reactor.run()
        finally:
            termios.tcsetattr ( fd, termios.TCSANOW, oldSettings )
            os.write ( fd, "\r\x1bc\r" )

        return service.Service.startService ( self )

######################################################################
# Create the application service hierarchy.
######################################################################

application       = service.Application ( 'cliapp' )

cs = CLIService()
cs.setServiceParent ( application )




More information about the Twisted-Python mailing list