Initial import to Tizen
[profile/ivi/python-twisted.git] / doc / historic / 2003 / pycon / applications / applications.html
1 <?xml version="1.0"?>                                                                           
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
5 <html>                      
6   <head>
7     <title>Applications of the Twisted Framework</title>
8     <link href="stylesheet.css" type="text/css" rel="stylesheet" />                                                                
9   </head>                                                                                                                           
10   <body>                                                                                                                   
11     <h1>Applications of the Twisted Framework</h1>
12     <p>Jp Calderone</p>
13     <p>exarkun@twistedmatrix.com</p>
14
15     <h2>ABSTRACT</h2>
16
17     <p>Two projects developed using the Twisted framework are described;
18     one, Twisted.names, which is included as part of the Twisted
19     distribution, a domain name server and client API, and one, Pynfo, which
20     is packaged separately, a network information robot.</p>
21
22     <h2>Twisted (dot) Names</h2>
23
24     <h3>Motivation</h3>
25     <p>The field of domain name servers is well explored and numerous
26     strong, widely-deployed implementations of the protocol exist.  DNSSEC,
27     IPv6, service location, geographical location, and many of the other DNS
28     extension proposals all have high quality support in BIND, djbdns,
29     maradns, and others.  From a client's perspective, though, the landscape
30     looks a little different.  APIs to perform arbitrary domain name lookups
31     are sparse.  In contrast, Twisted.names presents a richly featured,
32     asynchronous client API.</p>
33
34     <h3>Names Server</h3>
35     <p><b>Names</b> is capable of operating as a fully functional domain
36     name server.  It implements caching, recursive lookups, and can act as
37     the authority for an arbitrary number of domains.  It is not, however, a
38     finely tuned performance machine.  Responding to queries can take about
39     twice the time other domain name servers might need.  It has not been
40     investigated whether this is a design limitation or merely the result of
41     an unoptimized implementation.</p>
42
43     <h3>Names Client</h3>
44     <p>As a client, <b>Names</b> provides an easy interface to every type of
45     record supported by.  Looking up the MX records for a host, for example,
46     might look like this:</p>
47
48     <pre class="python">
49     def _cbMailExchange(results):
50         # Callback for MX query
51         answers = results[0]
52         print 'Mail Exchange is: ', answers
53
54     def _ebMailExchange(failure):
55         # Error callback for MX query
56         print 'Lookup failed:'
57         failure.printTraceback()
58
59     from twisted.names import client
60     d = client.lookupMailExchange('example-domain.com')
61     d.addCallbacks(_cbMailExchange, _ebMailExchange)
62     </pre>
63
64     <p>Looking up other record types is as simple as calling a different
65     <code>lookup*</code> function.</p>
66
67     <h3>Implementation</h3> 
68
69     <p>As with most network software written using Twisted, the first step
70     in developing <b>Names</b> was to write the protocol support.  In this
71     case, the protocol was DNS, and support was partially implemented. 
72     However, it attempted to merge support for both UDP and TCP, and ended
73     up with less than optimal results.  Much of this code was discarded,
74     though some of the lowest level encoding and decoding code worked well
75     and was re-used.</p>
76
77     <p>With the two protocol classes, DNSDatagramProtocol and DNSProtocol
78     (the TCP version) implemented, the next step was to write classes which
79     created the proper behavior for a domain name server.  This logic was
80     put in the <code>twisted.names.server.DNSServerFactory</code> class,
81     which in turn relies on several different kind of <code>Resolver</code>s
82     to find the appropriate response to queries it receives from the
83     protocol instance.</p>
84
85     <p>The chain of execution, then, is this: a packet is received by the
86     protocol object (a <code>DNSDatagramProtocol</code> or
87     <code>DNSProtocol</code> instance); the packet is decoded by
88     <code>twisted.protocols.dns.RRHeader</code> in cooperation with one of
89     the record classes (<code>twisted.protocols.dns.Record_A</code> for
90     example); the decoded <code>twisted.protocols.dns.Query</code> object is
91     passed up to the <code>twisted.names.server.DNSServerFactory</code>,
92     which determines the query type and invokes the appropriate lookup
93     method on each of its resolver objects in turn; if an answer is found,
94     it is passed back down to the protocol instance (otherwise the
95     appropriate bit for an error condition is set), where it is encoded and
96     transmitted back to the client.</p>
97
98     <p>There are four kinds of resolvers in the current implementation.  The
99     first three are authorities, caches, and recursive resolvers.  They are
100     generally queried, in this order, using the fourth resolver, the "chain"
101     resolver, which simply queries the resolvers it knows about, moving on
102     to the next when any given resolver fails to produce a response, and
103     generating the proper exception when the last resolver has failed.</p>
104
105     <h3>Shortcomings</h3>
106     
107     <p>There are several aspects of Twisted Names that might preclude its
108     use in "production" software.  These issues stem mainly from its
109     immaturity, it being less than six months old at the writing of this
110     paper.</p>
111
112     <ul>
113     <li><p>Possibly of foremost interest to those who might use it in a
114     high-load environment, it has somewhat poor runtime performance
115     characteristics.  One potential reason for this is the extensive use of
116     exceptions to signal the relatively common case of a resolver lookup
117     failing.  Solutions to this problem are apparent, but an implementation
118     change has not been attempted.  Until this area of its development is
119     more fully examined, it will likely not be of use in anything other than
120     for low- to mid-load tasks, or with more hardware available to it than
121     might seem reasonable.</p></li>
122
123     <li>No attempt has been made to implement DNSSEC.</li>
124
125     <li>Certain areas of the server remain out of compliance with the
126     standardized RFCs, occasionally causing undesirable behavior when
127     interacting with clients.  This most frequently manifests itself as a
128     lookup which fails the first time and succeeds on subsequent attempts.
129     It is not believed that these represent architectural flaws, only small
130     oversights in areas such as the "additional processing" sections of the
131     current authority resolver implementations.</li>
132     </ul>
133     <h2>Pynfo</h2>
134
135     <h3>Motivation</h3>
136     <p>Pynfo was originally begun as a learning project to become acquainted
137     with the Twisted framework.  After a brief initial development period
138     and an extended period of non-development, Pynfo was picked up again to
139     serve as a replacement for several existing robots, each with fragile
140     code bases and with designs not intended for future integration with
141     other services.  After it subsumed the functions of network relaying and
142     Google searches, other desired features, which enhanced the IRC medium
143     and had not previously been considered due to the difficulty of
144     extending existing robots, were added to Pynfo, prompting the development
145     of an elementary plug-in system to further facilitate the integration
146     process.</p>
147
148     <h3>Architecture</h3>
149     <p>Pynfo performs such simple tasks as noting the last time an
150     individual spoke and querying the Google search engine, as well as
151     several more complex operations like relaying traffic between different
152     IRC networks and publishing channel logs through an HTTP interface.</p>
153
154     <p>Toward these ends, it is useful to abstract the functionality into
155     several different layers:</p>
156
157     <ul>
158     <li><p>The factory:  All shared data, such as the channels a given user is
159     known to be in, the plugins currently loaded, and the addresses of servers
160     to connect to, is aggregated here.  When it is necessary to make a
161     connection, the factory creates an instance of the appropriate Protocol
162     subclass, in a manner similar to this:
163
164     <pre class="python">
165     def buildProtocol(self, address):
166         for net in self.data['networks'].values():
167             if net.address == address:
168                 break
169
170         proto = IRCProtocol(net)
171         self.allBots[net.alias] = proto
172         proto.factory = self
173         return proto
174     </pre>
175
176     The factory instance is created only once, and that instance persists
177     through the entire time a particular Pynfo bot operates.</p>
178     </li>
179
180     <li><p>The protocol: Each kind of service Pynfo can connect to has a
181     Protocol class associated with it, a class which handles the specifics
182     of communicating over this protocol.  Unlike the factory, protocols
183     instances can be short lived and are created and destroyed as many times
184     as network connectivity demands.  When a Pynfo robot shuts down and is
185     serialized to disk, all Protocol instances are destroyed and discarded,
186     to be created anew when the robot is restarted.</p>
187     </li>
188
189     <li><p>Plugins: These give Pynfo most of its functionality.  From the
190     very simple logging module, which does no more than write strings to
191     disk, to the esoteric lookup module, which translates hostnames into
192     dotted-quads, to the informative dictionary module, which queries an <a
193     href="http://dict.org">online dictionary</a>, plugins come in all shapes
194     and sizes, and can be written to fill almost any niche.</p>
195     </li>
196     </ul>
197
198     <h3>Employing Components</h3> 
199     <p>Twisted provides a <i>component</i> system which Pynfo relies on to
200     split up useful functionality used in different areas of the code.  The
201     Interface class is the primary element in the component system, and is
202     used as a location for a semi-format definition of an API, as well as
203     documentation.  Classes declare that they implement an Interface by
204     including it in their __implements__ tuple attribute.  Interfaces can
205     also be added to classes by third parties using the registerAdapter()
206     function.  This takes an Adapter type in addition to the interface being
207     registered and the type it is being registered for.  Adapters are a
208     objects which can store additional state information and implement
209     functionality without being part of the classes that are "actually"
210     being operated upon.  They, as their name suggests, adapt components to
211     conform to interfaces.</p>
212
213     <p>Components can implement interfaces themselves, or maintain a cache
214     of adapter objects for each interfaces that is requested of them.  These
215     persist like any other attribute, and so state stored in adapters
216     remains associated with the component as long as that component exists, or
217     until the adapter is explicitly removed.</p>
218
219     <p>Pynfo's Factory class uses two adapters to implement two basic
220     Interfaces that many plugins find useful.  The first is the IStorage
221     interface.
222
223     <pre class="python">
224     class IStorage(components.Interface):
225
226         def store(self, key, version, value):
227             """
228             Store any pickleable object
229             """
230
231         def retrieve(self, key, version):
232             """
233             Retrieve the previously stored object associated with key and
234             version
235             """
236     </pre>
237     An example usage of this interface is the PyPI plugin, which polls the
238     Python Package Index and reports updates to a configurable list of
239     outputs:
240
241     <pre class="python">
242     def init(factory):
243         global notifyChannels
244         store = factory.getComponent(interfaces.IStorage)
245         try:
246             notifyChannels = store.retrieve('pypi', __version__)
247         except error.RetrievalError:
248             notifyChannels = []
249
250     </pre>
251     <p>The module requests the component of factory which implements
252     IStorage, then attempts to load any previously stored version of
253     "notifyChannels".  If none is found, it defaults to none.  In the
254     finalizer below, this global is stored, using the same interfaced, to be
255     retrieved when the module is next initialized.</p>
256
257     <pre class="python">
258     def fini(factory):
259         s = factory.getComponent(interfaces.IStorage)
260         s.store('pypi', __version__, notifyChannels)
261     </pre>
262
263     The second interface allows low granularity scheduling of events:
264     
265     <pre class="python">
266     class IScheduler(components.Interface):
267         MINUTELY = 60
268         HOURLY = 60 * 60
269         DAILY = 60 * 60 * 24
270         WEEKLY = 60 * 60 * 24 * 7
271
272
273         def schedule(self, period, fn, *args, **kw):
274             """
275             Cause a function to be invoked at regular intervals with the given
276             arguments.
277             """
278     </pre>
279     The Adapter which implements this interface is just as simple:
280     <pre class="python">
281     class SchedulerAdapter(components.Adapter):
282         __implements__ = (interfaces.IScheduler,)
283
284         def schedule(self, period, fn, *args, **kw):
285             from twisted.internet import reactor
286             def cycle():
287                 fn(*args, **kw)
288                 reactor.callLater(period, cycle)
289             reactor.callLater(period, cycle)
290     </pre>
291     </p>
292     
293     <p>Implementing these interfaces as adapters using the component system
294     has two primary advantages over a simple inheritance or mixins approach. 
295     First, it allows plugins to add completely new behavior to the system
296     without complex and fragile manipulation of the factory's __class__
297     attribute.  This is a big win when it comes to plugins that want to
298     share new functionality with other plugins.  For example, the "ignore"
299     plugin adds an IDiscriminating interface and an adapter which implements
300     it.  Once this plugin is loaded, any other plugin can request the
301     component for IDiscriminating and add users to or remove users from the
302     ignore list.</p>
303
304     <h3>The Plugin Framework</h3>
305
306     <p>Before a module can be loaded and initialized as a plugin, it must be
307     located.  This could be done with a simple use of
308     <code>os.listdir()</code>, or <code>__all__</code> could be set to include
309     each new plugin added.  Twisted provides another way, though.</p>
310
311     <p>The <code>twisted.python.plugin</code> provides the most high-level
312     interface to the plugin system, a function called
313     <code>getPlugIns</code>.  It usually takes one argument, a plugin type,
314     which is an arbitrary string used to categorize the different kinds of
315     plugins available on a system.  Twisted's own "mktap" tool uses the
316     "tap" plugin type.  For Pynfo, I have elected to use the "infobot"
317     string.  <code>getPlugIns("infobot")</code> searches the system (by way
318     of PYTHONPATH) for files named "plugins.tml".  These files contain
319     python source, and are run as such; a function, "register" is placed in
320     their namespace, and the most common action for them is to invoke this
321     function one or more times, providing information about a plugin.  Here
322     is a snippet from one which Pynfo uses:</p>
323
324     <pre class="python">
325     register(
326         "Weather",
327         "Pynfo.plugins.weather",
328         description="Commands to check the weather at "
329                     "various places around the world.",
330         type="infobot"
331     )
332     </pre>
333
334     <p>Any number of plugin.tml files may exist in the filesystem, allowing
335     per-user and even per-robot plugins to be installed, all without
336     modifying the Pynfo installation itself.
337
338     The second argument indicates the module which may be imported to get
339     this plugin.  Pynfo traverses the resulting list, importing these modules,
340     and initializing them if necessary.</p>
341
342   </body>
343 </html>