Introduction
This is the tenth part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger.
In this part, we separate the application code that launches a finger service from the library code which defines a finger service, placing the application in a Twisted Application Configuration (.tac) file. We also move configuration (such as HTML templates) into separate files. Configuration and deployment with .tac and twistd are introduced in Using the Twisted Application Framework.
Organization
Now this code, while quite modular and well-designed, isn't
properly organized. Everything above the application=
belongs in a
module, and the HTML templates all belong in separate files.
We can use the templateFile
and templateDirectory
attributes to indicate what HTML template file to use for each Page, and where
to look for it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
# organized-finger.tac # eg: twistd -ny organized-finger.tac import finger from twisted.internet import protocol, reactor, defer from twisted.spread import pb from twisted.web import resource, server from twisted.application import internet, service, strports from twisted.python import log application = service.Application('finger', uid=1, gid=1) f = finger.FingerService('/etc/users') serviceCollection = service.IServiceCollection(application) internet.TCPServer(79, finger.IFingerFactory(f) ).setServiceParent(serviceCollection) site = server.Site(resource.IResource(f)) internet.TCPServer(8000, site ).setServiceParent(serviceCollection) internet.SSLServer(443, site, finger.ServerContextFactory() ).setServiceParent(serviceCollection) i = finger.IIRCClientFactory(f) i.nickname = 'fingerbot' internet.TCPClient('irc.freenode.org', 6667, i ).setServiceParent(serviceCollection) internet.TCPServer(8889, pb.PBServerFactory(finger.IPerspectiveFinger(f)) ).setServiceParent(serviceCollection)
Note that our program is now quite separated. We have:
- Code (in the module)
- Configuration (file above)
- Presentation (templates)
- Content (
/etc/users
) - Deployment (twistd)
Easy Configuration
We can also supply easy configuration for common cases with a makeService method that will also help build .tap files later:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
# Easy configuration # makeService from finger module def makeService(config): # finger on port 79 s = service.MultiService() f = FingerService(config['file']) h = internet.TCPServer(79, IFingerFactory(f)) h.setServiceParent(s) # website on port 8000 r = resource.IResource(f) r.templateDirectory = config['templates'] site = server.Site(r) j = internet.TCPServer(8000, site) j.setServiceParent(s) # ssl on port 443 if config.get('ssl'): k = internet.SSLServer(443, site, ServerContextFactory()) k.setServiceParent(s) # irc fingerbot if 'ircnick' in config: i = IIRCClientFactory(f) i.nickname = config['ircnick'] ircserver = config['ircserver'] b = internet.TCPClient(ircserver, 6667, i) b.setServiceParent(s) # Pespective Broker on port 8889 if 'pbport' in config: m = internet.TCPServer( int(config['pbport']), pb.PBServerFactory(IPerspectiveFinger(f))) m.setServiceParent(s) return s
And we can write simpler files now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# simple-finger.tac # eg: twistd -ny simple-finger.tac from twisted.application import service import finger options = { 'file': '/etc/users', 'templates': '/usr/share/finger/templates', 'ircnick': 'fingerbot', 'ircserver': 'irc.freenode.net', 'pbport': 8889, 'ssl': 'ssl=0' } ser = finger.makeService(options) application = service.Application('finger', uid=1, gid=1) ser.setServiceParent(service.IServiceCollection(application))
% twistd -ny simple-finger.tac
Note: the finger user still has ultimate power: he can use
makeService
, or he can use the lower-level interface if he has
specific needs (maybe an IRC server on some other port? Maybe we want the
non-SSL webserver to listen only locally? etc. etc.) This is an important
design principle: never force a layer of abstraction: allow usage of layers of
abstractions.
The pasta theory of design:
- Spaghetti: each piece of code interacts with every other piece of code [can be implemented with GOTO, functions, objects]
- Lasagna: code has carefully designed layers. Each layer is, in theory independent. However low-level layers usually cannot be used easily, and high-level layers depend on low-level layers.
- Ravioli: each part of the code is useful by itself. There is a thin layer of interfaces between various parts [the sauce]. Each part can be usefully be used elsewhere.
- ...but sometimes, the user just wants to order
Ravioli
, so one coarse-grain easily definable layer of abstraction on top of it all can be useful.