There are many ways in which your code will be called by various parts of
the Twisted framework by the time you're done. The initial one we're going to
focus on here is a plug-in for the mktap
utility.
mktap
produces complete, runnable
Application
instances, so no additional work is necessary to make your code work with
twistd
. First we will go through the process of
creating a
plug-in that Twisted can find, then we make it adhere to the mktap
interface. Finally we will load that plug-in with a
server.
Motivation
Twisted can power anything connected to a network, from your corporate message-broadcasting network to your desktop IRC client. This is great for integrating lots of different tools, but can make it very difficult to understand how to use the Twisted platform most effectively.
You've probably come to Twisted thinking of it as a library of code to help you write an application. It is much more useful to think of your code as the library. Twisted is a framework. The difference between a framework and a library is that a developer's code will run a library's functions; whereas a framework runs the developer's functions.
The difference is subtle, but significant; there are a range of resources which have to be allocated and managed regarding start-up and shut-down of an process, such as spawning of threads and handling events. In the library model, you handle the management of these resources. If you use Twisted as a framework, though, it will manage these resources itself.
If you were using Twisted as a library, you would probably write a Python script that:
- installs the reactor you want (if you're not using the default);
- performs initialization — opens data files, starts
reactor.callLater
loops, etc. - calls
reactor.run()
; and - performs deinitialization.
There are two problems with this methodology: it requires a lot of boiler-plate code; and it introduces inflexibility into the design. The usual way to solve this kind of problem is to write configuration files, and it is no different in Twisted.
At this point, the standard thing to do would be to write a long, tedious and subtly wrong manual describing the configuration language. Rest assured, like every other project, Twisted has those. But the language is secondary, and will be described later — more important are the configuration objects.
Services, Persistence and Security, Oh My!
It is possible to think of any configuration language as designed to build a configuration object, which is then queried and acted upon by the program runtime.
In Twisted, this is literally true internally — the
master configuration object is
twisted.application.service.Application
.
However, there is virtually nothing you can do with this
object directly. This object is Componentized
—
it has different, orthogonal, aspects. You access these aspects
by using interfaces. Interfaces, for our purposes,
are just callables which return different aspects of the
Application
.
There are four interfaces supported, three of which are defined in
twisted.application.service
:
IService
;IServiceCollection
;IProcess
; andtwisted.persisted.sob.IPersistable
.
Constructing an application is done by calling it with a single argument — its name. The name influences some things, among them the default file it will persist to (which is why it is mandatory).
from twisted.application import service from twisted.persisted import sob application = service.Application("myapplication") s = service.IService(application) sc = service.IServiceCollection(application) proc = service.IProcess(application) per = sob.IPersistable(application)
Services
The central framework classes that you will deal with, both as a Twisted
developer and administrator, are services, which implement twisted.application.service.IService
. twisted.application.service.Application
creates the root of
a tree of services that form a twisted application. There is one Application
instance per Twisted process, and it is the
top-level manager of resources and handler of events in the Twisted framework.
(Unlike other frameworks, like Qt
or wxWindows
, in
Twisted you do not derive from Application
; rather you register
event handlers for it to call by adding Services to it.) To store
configuration data, as well as other information, Twisted serializes Application
instances, storing all services that have
been registered with them. Since the whole Application
instance is serialized, Twisted
configuration
files are significantly more comprehensive than those for
other systems. These files store everything related to a running Application
instance; in essence the full state of a
running process.
There are two interfaces relevant to services —IService
and IServiceCollection
. IService
represents
a state-aware container. That means the service is ready to be
notified of application start-ups and shutdowns. Services can be named
or unnamed. IServiceCollection
holds other services. It
is possible to get named services from it by name. All services can be
gotten from it via either indexing or iteration.
Services can have a parent. Parents are set using
setServiceParent
. Services are detached from their parent with
disownServiceParent
. The parent must always be something that
complies with the IServiceCollection
interface.
Most services will inherit from Service
. This class
will set an attribute running
to a true value
in startService
and to a false value in stopService
.
This attribute will always be false in just-unpersisted Service
s,
without regards to its value at the time the Service
was
persisted.
MultiService
implements both IService
and
IServiceCollection
. It is used to keep the services in
a hierarchy.
It is, of course, possible to write one's own services, but Twisted
comes out of the box with several services which are useful in writing
network applications. These are found in
twisted.application.internet
, including
TCPServer
,
TCPClient
, and
TimerService
.
To each reactor.listenFoo
method corresponds a service
named FooServer
. The arguments to its constructor are the
same as the arguments to the method. It calls the method on application
start-up, and stops listening on application shut-down.
To each reactor.connectFoo
methods corresponds a service
named FooClient
. The arguments to its constructor are the
same as the arguments to the method. It calls the method on application
start-up. It might, or might not, stop the connection on application
shut-down. (This limitation will be removed at some point, and guaranteed
disconnection will be implemented.)
The last service in twisted.application.internet
is
TimerService
. The constructor takes a period, a callable
and optionally arguments and keyword arguments. The service, when it
is running, will make sure the callable will be called every time the
period elapses.
String Ports
In Twisted, a ServerFactory
does not care what kind
of virtual reliable circuit it listens too — SSL, UNIX domain
sockets, TCP sockets or something else. However, the APIs for constructing
the various *Server
classes are different. When it is
necessary for a less sophisticated user to direct construction of such
a class, the twisted.application.strports
module comes
in handy. It contains a function service
which accepts
a description and a factory, and returns a service. The
description is a string in a mini-language designed to specify
ports. Full specifications are in the module docstrings.
Configuration
At some point, the objects for the configuration actually have
to be constructed. The easiest and simplest way to do it is to
use Python as the configuration mini-language. This format is called
TAC
and traditionally files with this format have the extension
.tac
.
TAC files need to be valid Python files, which construct a variable
named application
. This variable will be the configuration
object. The full power of Python is available, of course.
Here's an example:
# Import modules from twisted.application import service, internet from twisted.protocols import wire from twisted.internet import protocol # Construct the application application = service.Application("echo") # Get the IServiceCollection interface myService = service.IServiceCollection(application) # Create the protocol factory myFactory = protocol.ServerFactory() myFactory.protocol = wire.Echo # Create the (sole) server # Normally, the echo protocol lives on port 7, but since that # is a privileged port, for this example we'll use port 7001 myServer = internet.TCPServer(7001, myFactory) # Tie the service to the application myServer.setServiceParent(myService)
Note that setServiceParent
will, in fact, automatically
cast its argument to IServiceCollection
. So, more succinctly,
the above code can be written:
from twisted.application import service, internet from twisted.protocols import wire from twisted.internet import protocol application = service.Application("echo") myFactory = protocol.ServerFactory() myFactory.protocol = wire.Echo internet.TCPServer(7001, myFactory).setServiceParent(application)
TAC files are run with twistd -y
or
twistd --python
. The twistd
manpage
has more information, but a common way to run is
twistd -noy echo.tac
. This tells twistd
to not daemonize and not to try and save application on shutdown.