What happens when you want to write a Twisted application? Well, if you come into Twisted thinking of it as a library, you may decide a useful way is to write a Python script. This Python script would probably take the following actions:
reactor.callLater
loops, etc.reactor.run()
There are two problems with this methodology. It has a lot of boiler-plate code, and it introduces an 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.
It is possible to think of any configuration language as a
specially designed language to build a configuration object,
which is then queried and acted upon by the program runtime.
In Twisted, this is literally true internally -- and 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. Access to this aspects
is done by using interfaces. Interfaces, for our purposes,
are just callables which return different aspects of the
Application
.
Unlike other frameworks, like Qt
or wxWindows
,
in Twisted you do not derive from Application
--
you use methods to register your objects with it.
There are four interfaces supported, three of which are defined in
twisted.application.service
:
IService
IServiceCollection
IProcess
twisted.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)
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.
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.
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.