Debugging with twisted.manhole
There are two ways to add a manhole port to your application. One is a simple telnet-like shell, and the other is a Perspective Broker -based service that uses a special client named 'manhole'.
Using the Manhole Telnet Server
Let's suppose that we have the following application:
#! /usr/bin/python from twisted.internet.app import Application from twisted.internet.protocol import Factory from twisted.protocols.wire import QOTD app = Application("demo") # add QOTD server f = Factory() f.protocol = QOTD app.listenTCP(8123, f) app.run()
Once this is running, it would be nice to poke around inside it. We can add the manhole-shell by adding a few lines to create a new server (a Factory) listening on a different point:
#! /usr/bin/python from twisted.internet.app import Application from twisted.internet.protocol import Factory from twisted.protocols.wire import QOTD import twisted.manhole.telnet app = Application("demo") # add QOTD server f = Factory() f.protocol = QOTD app.listenTCP(8123, f) # Add a manhole shell f = twisted.manhole.telnet.ShellFactory() f.username = "boss" f.password = "sekrit" f.namespace['foo'] = 12 app.listenTCP(8007, f) app.run()
With this in place, you can telnet to port 8007, give the username "boss" and password "sekrit", and you'll end up with a shell that behaves very much like the Python interpreter that you get by running python all by itself.
% telnet localhost 8007 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. twisted.manhole.telnet.ShellFactory Twisted 0.99.2 username: boss password: ***** >>>
(note that the original Quote-Of-The-Day server is still running on port 8123)
% nc localhost 8123 An apple a day keeps the doctor away.
The initial namespace of the manhole interpreter is defined by a dictionary stored in the 'namespace' attribute of the ShellFactory. For convenience, you can put references to any objects you like in that dict, and then retrieve them by name from the telnet session.
>>> foo 12
Of course we can change that namespace by evaluating expressions in the
interpreter. To be a useful debugging tool, however, we want to get access
to our servers (the protocol Factory
instances and everything
hanging off of them). We start by gaining access to the main Application
instance through
a global variable stored in the app module:
>>> import twisted.internet.app >>> a = twisted.internet.app.theApplication >>> a <'demo' app>
This object holds three things of interest: the list of Delayeds (functions scheduled to run some number of seconds in the future), the list of Services (subclasses of ApplicationService that have been added to the application, most notably Perspective Broker services), and the list of ports on which protocol Factories are listening. The ports are kept in a list, and the Factory object itself is available inside that list:
>>> a.tcpPorts [(8123, <twisted.internet.protocol.Factory instance at 0x8249b8c>, 5, ''), (8007, <twisted.manhole.telnet.ShellFactory instance at 0x824aefc>, 5, '')] >>> f = a.tcpPorts[0][1] >>> f <twisted.internet.protocol.Factory instance at 0x8249b8c>
Now that we have access to that Factory, what can we do? We can modify any attribute of the object, or call functions on it. Remember that the Factory stores a reference to a subclass of Protocol, and it uses that reference to create new Protocol instances for each new connection. We can change that reference to make the Factory create something else:
>>> from twisted.protocols.wire import Daytime >>> f.protocol = Daytime
By doing this, we have transformed the QOTD server into a Daytime server. The next time we connect to the Factory, it will give us the new kind of Protocol object:
% nc localhost 8123 Sat Sep 28 09:11:37 2002
Using the Manhole PB Client
The second service offered by twisted.manhole is a Perspective Broker -based server. This gives the client a remote reference to a twisted.manhole.service.Service object, which offers remotely-callable methods to evaluate python code. With the rich remote-method-invocation facilities provided by PB, however, much more is possible: the client can ask to "watch" certain objects, and then will receive messages every time that python object is changed. (this takes advantage of some twisted.python code that "hooks" some functions, like .setattr).
To add a manhole PB Service to your application, you need to do some PB setup first. This involves creating an Authorizer, creating a Perspective and Identity, assigning a username and password to the Identity, then finally creating the Service. These steps are part of the normal initialization of any PB application, but when you're just starting with Twisted, you're probably writing simple servers using .listenTCP, and haven't dealt with all those new classes yet. There is a shortcut that takes advantage of some of the ".tap" configuration code. It isn't pretty or clear, but adding the marked four lines will result in a password-protected Manhole Perspective being added to your application:
#! /usr/bin/python from twisted.internet.app import Application from twisted.internet.protocol import Factory from twisted.protocols.wire import QOTD app = Application("demo") # add QOTD server f = Factory() f.protocol = QOTD app.listenTCP(8123, f) # add manhole with username and password from twisted.tap.manhole import Options, updateApplication o = Options() o.parseOptions(["--user", "boss", "--password", "sekrit"]) updateApplication(app, o) app.run()
With this in place and running, you're ready to connect with the manhole client. This is a Gtk+-based GUI application named manhole installed along with the rest of twisted. When you run it, it brings up a dialog that asks for hostname, port number, Service name, username, and password (and also "Perspective" but don't worry about that for now). Use the default host/port of localhost/8787, and use boss/sekrit for the username and password.
Once the connection is established, you'll get a window that has an output area in the top, and an input area at the bottom. This is just like the python interpreter accessed through the telnet shell, but with a different GUI.
Other features are described in the manhole howto.
The PB-based manhole service has two separate namespaces: one is in the service and is retained across connections, while the other is in the per-connection Perspective. To reduce the typing you have to do upon each manhole connection, you can add items to the service's namespace as follows:
n = app.getServiceNamed("twisted.manhole").namespace n["a"] = app n["f"] = f n["help"] = """ app: the main Application object f: something named f """
These will then be available as a and f from all manhole sessions. Adding a variable called help is a nice touch: when you type 'help' in your manhole session, you'll get a reminder of what objects you've made easily available.
