~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/doc/historic/2004/ibm/talk.html

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<html><head><title>Twisted: A Tutorial</title></head><body>
 
2
 
 
3
<h1>Twisted: A Tutorial</h1>
 
4
 
 
5
<h2>Thanks</h2>
 
6
 
 
7
<p>I am grateful to IBM for inviting me to talk here, and to Muli Ben-Yehuda for arranging everything.</p> 
 
8
 
 
9
<h2>Administrative Notes</h2>
 
10
 
 
11
<p>After reading Peter Norvig's infamous <q>The Gettysburg Powerpoint Presentation</q>, I was traumatized enough to forgoe the usual bullets and slides style, which originally was developed for physical slide projectors. Therefore, these notes are presented as one long HTML file, and I will use a new invention I call the <q>scrollbar</q> to show just one thing at a time. Enjoy living on the bleeding edge of presentation technology!</p>
 
12
 
 
13
<h2>What Is Twisted?</h2>
 
14
 
 
15
<p>Twisted is an <em>event-based networkings framework for Python</em>. It includes not only the basics of networking but also high-level protocol implementations, scheduling and more. It uses Python's high-level nature to enable few dependencies between different parts of the code. Twisted allows you to write network applications, clients and servers, without using threads and without running into icky concurrency issues.</p>
 
16
 
 
17
<blockquote>
 
18
A computer is a state machine.
 
19
Threads are for people who can't program state machines. 
 
20
</blockquote>
 
21
 
 
22
<p>Alan Cox in a discussion about the threads and the Linux scheduler</p>
 
23
<p>http://www.bitmover.com/lm/quotes.html</p>
 
24
 
 
25
<h2>An Extremely Short Introduction to Python</h2>
 
26
 
 
27
<p>Python is a high-level dyanmically strongly typed language. All values are references to objects, and all arguments passed are objects references. Assignment changes the reference a variable points to, not the reference itself. Data types include integers (machine sized and big nums) like <code>1</code> and <code>1L</code>, strings and unicode strings like <code>"moshe"</code> and <code>u"\u05DE\u05E9\u05D4 -- moshe"</code>, lists (variably typed arrays, really) like <code>[1,2,3, "lalala", 10L, [1,2]]</code>, tuples (immutable arrays) like <code>("1", 2, 3)</code>, dictionaries <code>{"moshe": "person", "table": "thing"}</code> and user-defined objects.</p>
 
28
 
 
29
<p>Every Python object has a type, which is itself a Python object. Some types aare defined in native C code (such as the types above) and some are defined in Python using the class keyword.</p>
 
30
 
 
31
<p>Structure is indicated through indentation.</p>
 
32
 
 
33
<p>Functions are defined using</p>
 
34
 
 
35
<pre class="py-listing">
 
36
def function(param1, param2, optionalParam="default value", *restParams,
 
37
             **keywordParams):
 
38
    pass
 
39
</pre>
 
40
 
 
41
<p>And are called using <code>function("value for param1", param2=42,
 
42
optionalParam=[1,2], "these", "params", "will", "be", "restParams",
 
43
keyword="arguments", arePut="in dictionary keywordParams")</code>.</p>
 
44
 
 
45
<p>Functions can be defined inside classes:</p>
 
46
 
 
47
<pre class="py-listing">
 
48
class Example:
 
49
    # constructor
 
50
    def __init__(self, a=1):
 
51
        self.b = a
 
52
    def echo(self):
 
53
        print self.b
 
54
e = Example(5)
 
55
e.echo()
 
56
</pre>
 
57
 
 
58
<p>All methods magically receive the self argument, but must treat it
 
59
explicitly.</p>
 
60
 
 
61
<p>Functions defined inside functions enjoy lexical scoping. All variables
 
62
are outer-scope unless they are assigned to in which case they are inner-most
 
63
scope.</p>
 
64
 
 
65
<h2>How To Use Twisted</h2>
 
66
 
 
67
<p>Those of you used to other event-based frameworks (notably, GUIs) will recognize the familiar pattern -- you call the framework's <code>mainloop</code> function, and it calls registered event handlers. Event handlers must finish quickly, to enable the framework to call other handlers without forcing the client (be it a GUI user or a network client) to wait. Twisted uses the <code>reactor</code> module for the main interaction with the network, and the main loop function is called <code>reactor.run</code>. The following code is the basic skeleton of a Twisted application.</p>
 
68
 
 
69
<pre class="py-listing">
 
70
from twisted.internet import reactor
 
71
reactor.run()
 
72
</pre>
 
73
 
 
74
<p>This runs the reactor. This takes no CPU on UNIX-like systems, and little CPU on Windows (some APIs must be busy-polled), runs forever and does not quit unless delivered a signal.</p>
 
75
 
 
76
<h2>How To Use Twisted to Do Nothing</h2>
 
77
 
 
78
<p>Our first task using Twisted is to build a server to the well-known <q>finger</q> protocol -- or rather a simpler variant of it. The first step is accepting, and hanging, connections. This example will run forever, and will allow clients to connect to port 1079. It will promptly ignore everything they have to say...</p>
 
79
 
 
80
 
 
81
<pre class="py-listing">
 
82
from twisted.internet import protocol, reactor
 
83
class FingerProtocol(protocol.Protocol):
 
84
    pass
 
85
class FingerFactory(protocol.ServerFactory):
 
86
    protocol = FingerProtocol
 
87
reactor.listenTCP(1079, FingerFactory())
 
88
reactor.run()
 
89
</pre>
 
90
 
 
91
<p>The protocol class is empty -- the default network event handlers simply throw away the events. Notice that the <code>protocol</code> attribute in <code>FingerFactory</code> is the <code>FingerProtocol</code> class itself, not an instance of it. Protocol logic properly belongs in the <code>Protocol</code> subclass, and the next few slides will show it developing.</p>
 
92
 
 
93
 
 
94
<h2>How To Use Twisted to Do Nothing (But Work Hard)</h2>
 
95
 
 
96
<p>The previous example used the fact that the default event handlers in the protocol exist and do nothing. The following example shows how to code the event handlers explicitly to do nothing. While being no more useful than the previous version, this shows the available events.</p>
 
97
<pre class="py-listing">
 
98
from twisted.internet import protocol, reactor
 
99
class FingerProtocol(protocol.Protocol):
 
100
    def connectionMade(self):
 
101
        pass
 
102
    def connectionLost(self):
 
103
        pass
 
104
    def dataReceived(self, data):
 
105
        pass
 
106
class FingerFactory(protocol.ServerFactory):
 
107
    protocol = FingerProtocol
 
108
reactor.listenTCP(1079, FingerFactory())
 
109
reactor.run()
 
110
</pre>
 
111
 
 
112
<p>This example is much easier to work with for the copy'n'paste style of programming...it has everything a good network application has: a low-level protocol implementation, a high-level class to handle persistent configuration data (the factory) and enough glue code to connect it to the network.</p>
 
113
 
 
114
<h2>How To Use Twisted to Be Rude</h2>
 
115
 
 
116
<p>The simplest event to respond to is the connection event. It is the first event a connection receives. We will use this opportunity to slam the door shut -- anyone who connects to us will be disconnected immediately.</p>
 
117
 
 
118
<pre class="py-listing">
 
119
class FingerProtocol(protocol.Protocol):
 
120
    def connectionMade(self):
 
121
        self.transport.loseConnection()
 
122
</pre>
 
123
 
 
124
<p>The <code>transport</code> attribute is the protocol's link to the other side. It uses it to send data, to disconnect, to access meta-data about the connection and so on. Seperating the transport from the protocol enables easier work with other kinds of connections (unix domain sockets, SSL, files and even pre-written for strings, for testing purposes). It conforms to the <code>ITransport</code> interface, which is defined in <code>twisted.internet.interfaces</code>.</p>
 
125
 
 
126
<h2>How To Use Twisted To Be Rude (In a Smart Way)</h2>
 
127
 
 
128
<p>The previous version closed the connection as soon as the client connected, not even appearing as though it was a problem with the input. Since finger is a line-oriented protocol, if we read a line and then terminate the connection, the client will be forever sure it was his fault.</p>
 
129
 
 
130
<pre class="py-listing">
 
131
from twisted.protocols import basic
 
132
class FingerProtocol(basic.LineReceiver):
 
133
    def lineReceived(self, user):
 
134
        self.transport.loseConnection()
 
135
</pre>
 
136
 
 
137
<p>We now inherit from <code>LineReceiver</code>, and not directly from <code>Protocol</code>. <code>LineReceiver</code> allows us to respond to network data line-by-line rather than as they come from the TCP driver. We finish reading the line, and only then we disconnect the client. Important note for people used to various <code>fgets</code>, <code>fin.readline()</code> or Perl's <code>&lt;&gt;</code> operator: the line does <em>not</em> end in a newline, and so an empty line is <em>not</em> an indicator of end-of-file, but rather an indication of an empty line. End-of-file, in network context, is known as <q>closed connection</q> and is signaled by another event altogether (namely, <code>connectionLost</code>.</p>
 
138
 
 
139
<h2>How To Use Twisted to Output Errors</h2>
 
140
 
 
141
<p>The limit case of a useful finger server is a one with no users. This server will always reply that such a user does not exist. It can be installed while a system is upgraded or the old finger server has a security hole.</p>
 
142
 
 
143
<pre class="py-listing">
 
144
from twisted.protocols import basic
 
145
class FingerProtocol(basic.LineReceiver):
 
146
    def lineReceived(self, user):
 
147
        self.transport.write("No such user\r\n")
 
148
        self.transport.loseConnection()
 
149
</pre>
 
150
 
 
151
<p>Notice how we did not have to explicitly flush, or worry about the write being successful. Twisted will not close the socket until it has written all data to it, and will buffer it internally. While there are ways for interacting with the buffering mechanism (for example, when sending large amounts of data), for simple protocols this proves to be convenient.</p>
 
152
 
 
153
<h2>How to Use Twisted to Do Useful Things</h2>
 
154
 
 
155
<p>Note how we remarked earlier that <em>protocol logic</em> belongs in the
 
156
protocol class. This is necessary and sufficient -- we do not want non-protocol
 
157
logic in the protocol class. User management is clearly not part of the protocol logic, and so should not be in the protocol. This is exactly why we have the factory in the first place. The factory allows us to delegate non-protocol logic
 
158
to a seperate class. It is often not completely trivial what does and does not belong in the factory, of course.</p>
 
159
 
 
160
<pre class="py-listing">
 
161
from twisted.protocols import basic
 
162
class FingerProtocol(basic.LineReceiver):
 
163
    def lineReceived(self, user):
 
164
        self.transport.write(self.factory.getUser(user)+"\r\n")
 
165
        self.transport.loseConnection()
 
166
class FingerFactory(protocol.ServerFactory):
 
167
    protocol = FingerProtocol
 
168
    def getUser(self, user):
 
169
        return "No such user"
 
170
</pre>
 
171
 
 
172
<p>Notice how we did not change the observable behaviour, but we did make the factory know about which users exist and do not exist. With this kind of setup, we will not need to modify our protocol class when we change user management schemes...hopefully.</p>
 
173
 
 
174
<h2>Using Twisted to Do Useful Things (For Real)</h2>
 
175
 
 
176
<p>The last heading did not live up to its name -- the server kept spouting off that it did not know who we are talking about, they never lived here and could we please go away. It did, however, prepare the way for doing actually useful things which we do here.</p>
 
177
 
 
178
<pre class="py-listing">
 
179
class FingerFactory(protocol.ServerFactory):
 
180
    protocol = FingerProtocol
 
181
    def __init__(self, **kwargs):
 
182
        self.users = kwargs
 
183
    def getUser(self, user):
 
184
        return self.users.get(user, "No such user")
 
185
reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
 
186
</pre>
 
187
 
 
188
<p>This server actually has valid use cases. With such code, we could easily disseminate office/phone/real name information across an organization, if people had finger clients.</p>
 
189
 
 
190
<h2>Using Twisted to Do Useful Things, Correctly</h2>
 
191
 
 
192
<p>The version above works just fine. However, the interface between the protocol class and its factory is synchronous. This might be a problem. After all, <code>lineReceived</code> is an event, and should be handled quickly. If the user's status needs to be fetched by a slow process, this is impossible to achieve using the current interface. Following our method earlier, we modify this API glitch without changing anything in the outward-facing behaviour.</p>
 
193
 
 
194
 
 
195
<pre class="py-listing">
 
196
from twisted.internet import defer
 
197
class FingerProtocol(basic.LineReceiver):
 
198
    def lineReceived(self, user):
 
199
        d = defer.maybeDeferred(self.factory.getUser, user)
 
200
        def e(_):
 
201
            return "Internal error in server"
 
202
        d.addErrback(e)
 
203
        def _(m):
 
204
            self.transport.write(m+"\r\n")
 
205
            self.transport.loseConnection()
 
206
        d.addCallback(_)
 
207
</pre>
 
208
 
 
209
<p>The value of using <code>maybeDeferred</code> is that it seamlessly
 
210
works with the old factory too. If we would allow changing the factory,
 
211
we could make the code a little cleaner, as the following example shows.</p> 
 
212
 
 
213
<pre class="py-listing">
 
214
from twisted.internet import defer
 
215
class FingerProtocol(basic.LineReceiver):
 
216
    def lineReceived(self, user):
 
217
        d = self.factory.getUser( user)
 
218
        def e(_):
 
219
            return "Internal error in server"
 
220
        d.addErrback(e)
 
221
        def _(m):
 
222
            self.transport.write(m+"\r\n")
 
223
            self.transport.loseConnection()
 
224
        d.addCallback(_)
 
225
class FingerFactory(protocol.ServerFactory):
 
226
    protocol = FingerProtocol
 
227
    def __init__(self, **kwargs):
 
228
        self.users = kwargs
 
229
    def getUser(self, user):
 
230
        return defer.succeed(self.users.get(user, "No such user"))
 
231
</pre>
 
232
 
 
233
<p>Note how this example had to change the factory too. <code>defer.succeed</code> is a way to returning a deferred results which is already triggered successfully. It is useful in exactly these kinds of cases: an API had to be asynchronous to support other use-cases, but in a simple enough use-case, the result is availble immediately.</p>
 
234
 
 
235
<p>Deferreds are abstractions of callbacks. In this instance, the deferred
 
236
had a value immediately, so the callback was called as soon as it was
 
237
added. We will soon show an example where it will not be available immediately.
 
238
The errback is called if there are problems, and is equivalent to exception handling. If it returns a value, the exception is considered handled, and further callbacks will be called with its return value.</p>
 
239
 
 
240
<h2>Using Twisted to Do The Standard Thing</h2>
 
241
 
 
242
<p>The standard finger daemon is equivalent to running the <code>finger</code>
 
243
command on the remote machine. Twisted can treat processes as event sources too, and enables high-level abstractions to allow us to get process output easily.</p>
 
244
 
 
245
<pre class="py-listing">
 
246
from twisted.internet import utils
 
247
class FingerFactory(protocol.ServerFactory):
 
248
    protocol = FingerProtocol
 
249
    def getUser(self, user):
 
250
        return utils.getProcessOutput("finger", [user])
 
251
</pre>
 
252
 
 
253
<p>The real value of using deferreds in Twisted is shown here in full. Because there is a standard way to abstract callbacks, especially a way that does not require sending down the call-sequence a callback, all functions in Twisted itself whose result might take a long time return a deferred. This enables us in many cases to return the value that a function returns, without caring that it is deferred at all.</p>
 
254
 
 
255
<p>If the command exits with an error code, or sends data to stderr, the
 
256
errback will be triggered and the user will be faced with a half-way useful
 
257
error message. Since we did not whitewash the argument at all, it is quite
 
258
likely that this contains a security hole. This is, of course, another
 
259
standard feature of finger daemons...</p>
 
260
 
 
261
<p>However, it is easy to whitewash the output. Suppose, for example, we do not want the explicit name <q>Microsoft</q> in the output, because of the risk of offending religious feelings. It is easy to change the deferred into one which is completely safe.</p>
 
262
 
 
263
<pre class="py-listing">
 
264
from twisted.internet import utils
 
265
class FingerFactory(protocol.ServerFactory):
 
266
    protocol = FingerProtocol
 
267
    def getUser(self, user):
 
268
        d = utils.getProcessOutput("finger", [user])
 
269
        def _(s):
 
270
            return s.replace('Microsoft', 'It which cannot be named')
 
271
        d.addCallback(_)
 
272
        return d
 
273
</pre>
 
274
 
 
275
<p>The good news is that the protocol class will need to change no more,
 
276
up until the end of the talk. That class abstracts the protocol well
 
277
enough that we only have to modify factories when we need to support
 
278
other user-management schemes.</p>
 
279
 
 
280
<h2>Use The Correct Port</h2>
 
281
 
 
282
<p>So far we used port 1097, because with UNIX low ports can only be bound by root. Certainly we do not want to run the whole finger server as root. The usual solution would be to use privilege shedding: something like <code>reactor.listenTCP</code>, followed by appropriate <code>os.setuid</code> and then <code>reactor.run</code>. This kind of code, however, brings the option of making subtle bugs in the exact place they are most harmful. Fortunately, Twisted can help us do privilege shedding in an easy, portable and safe manner.</p>
 
283
 
 
284
<p>For that, we will not write <code>.py</code> main programs which run the application. Rather, we will write <code>.tac</code> (Twisted Application Configuration) files which contain the configuration. While Twisted supports several configuration formats, the easiest one to edit by hand, and the most popular one is...Python. A <code>.tac</code> is just a plain Python file which defines a variable named <code>application</code>. That variable should subscribe to various interfaces, and the usual way is to instantiate <code>twisted.service.Application</code>. Note that unlike many popular frameworks, in Twisted it is not recommended to <em>inherit</em> from <code>Application</code>.</p>
 
285
 
 
286
<pre class="py-listing">
 
287
from twisted.application import service
 
288
application = service.Application('finger', uid=1, gid=1)
 
289
factory = FingerFactory(moshez='Happy and well')
 
290
internet.TCPServer(79, factory).setServiceParent(application)
 
291
</pre>
 
292
 
 
293
<p>This is a minimalist <code>.tac</code> file. The application class itelf is resopnsible for the uid/gid, and various services we configure as its children are responsible for specific tasks. The service tree really is a tree, by the way...</p>
 
294
 
 
295
<h2>Running TAC Files</h2>
 
296
 
 
297
<p>TAC files are run with <code>twistd</code> (TWISTed Daemonizer). It supports various options, but the usual testing way is:</p>
 
298
 
 
299
<pre class="shell">
 
300
root% twistd -noy finger.tac
 
301
</pre>
 
302
 
 
303
<p>With long options:</p>
 
304
 
 
305
<pre class="shell">
 
306
root% twistd --nodaemon --no_save --python finger.tac
 
307
</pre>
 
308
 
 
309
<p>Stopping <code>twistd</code> from daemonizing is convenient because then it is possible to kill it with CTRL-C. Stopping it from saving program state is good because recovering from saved states is uncommon and problematic and it leaves too many <code>-shutdown.tap</code> files around. <code>--python finger.tac</code> lets <code>twistd</code> know what type of configuration to read from which file. Other options include <code>--file .tap</code> (a pickle), <code>--xml .tax</code> (an XML configuration format) and <code>--source .tas</code> (a specialized Python-source format which is more regular, more verbose and hard to edit).</p>
 
310
 
 
311
<h2>Integrating Several Services</h2>
 
312
 
 
313
<p>Before we can integrate several services, we need to write another service. The service we will implement here will allow users to change their status on the finger server. We will not implement any access control. First, the protocol class:</p>
 
314
 
 
315
<pre class="py-listing">
 
316
class FingerSetterProtocol(basic.LineReceiver):
 
317
    def connectionMade(self):
 
318
        self.lines = []
 
319
    def lineReceived(self, line):
 
320
        self.lines.append(line)
 
321
    def connectionLost(self, reason):
 
322
        self.factory.setUser(self.line[0], self.line[1])
 
323
</pre>
 
324
 
 
325
<p>And then, the factory:</p>
 
326
 
 
327
<pre class="py-listing">
 
328
class FingerSetterFactory(protocol.ServerFactory):
 
329
    protocol = FingerSetterProtocol
 
330
    def __init__(self, fingerFactory):
 
331
        self.fingerFactory = fingerFactory
 
332
    def setUser(self, user, status):
 
333
        self.fingerFactory.users[user] = status
 
334
</pre>
 
335
 
 
336
<p>And finally, the <code>.tac</code>:</p>
 
337
 
 
338
<pre class="py-listing">
 
339
ff = FingerFactory(moshez="Happy and well")
 
340
fsf = FingerSetterFactory(ff)
 
341
application = service.Application('finger', uid=1, gid=1)
 
342
internet.TCPServer(79,ff).setServiceParent(application)
 
343
internet.TCPServer(1079,fsf,interface='127.0.0.1').setServiceParent(application)
 
344
</pre>
 
345
 
 
346
<p>Now users can use programs like <code>telnet</code> or <code>nc</code> to change their status, or maybe even write specialized programs to set their options:</p>
 
347
 
 
348
<pre class="py-listing">
 
349
import socket
 
350
s = socket.socket()
 
351
s.connect(('localhost', 1097))
 
352
s.send('%s\r\n%s\r\n' % (sys.argv[1], sys.argv[2]))
 
353
</pre>
 
354
 
 
355
<p>(Later, we will learn on how to write network clients with Twisted, which fix the bugs in this example.)</p>
 
356
 
 
357
<p>Note how, as a naive version of access control, we bound the setter service to the local machine, not to the default interface (<code>0.0.0.0</code). Thus, only users with shell access to the machine will be able to change settings. It is possible to do more access control, such as listening on UNIX domain sockets and accessing various unportable APIs to query users. There will be no examples of such techniques in this talk, however.</p>
 
358
 
 
359
<h2>Integrating Several Services: The Smart Way</h2>
 
360
 
 
361
<p>The last example exposed a historical asymmetry. Because the finger setter was developed later, it poked into the finger factory in an unseemly manner. Note that now, we will only be changing factories and configuration -- the protocol classes, apparently, are perfect.</p>
 
362
 
 
363
<pre class="py-listing">
 
364
class FingerService(service.Service):
 
365
    def __init__(self, **kwargs):
 
366
        self.users = kwargs
 
367
    def getUser(self, user):
 
368
        return defer.succeed(self.users.get(user, "No such user"))
 
369
    def getFingerFactory(self):
 
370
        f = protocol.ServerFactory()
 
371
        f.protocol, f.getUser = FingerProtocol, self.getUser
 
372
        return f
 
373
    def getFingerSetterFactory(self):
 
374
        f = protocol.ServerFactory()
 
375
        f.protocol, f.setUser = FingerSetterProtocol, self.users.__setitem__
 
376
        return f
 
377
application = service.Application('finger', uid=1, gid=1)
 
378
f = FingerService(moshez='Happy and well')
 
379
ff = f.getFingerFactory()
 
380
fsf = f.getFingerSetterFactory()
 
381
internet.TCPServer(79,ff).setServiceParent(application)
 
382
internet.TCPServer(1079,fsf).setServiceParent(application)
 
383
</pre>
 
384
 
 
385
<p>Note how it is perfectly fine to use <code>ServerFactory</code> rather than subclassing it, as long as we explicitly set the <code>protocol</code> attribute -- and anything that the protocols use. This is common in the case where the factory only glues together the protocol and the configuration, rather than actually serving as the repository for the configuration information.</p>
 
386
 
 
387
<h2>Periodic Tasks</h2>
 
388
 
 
389
<p>In this example, we periodicially read a global configuration file to decide which users do what. First, the code.</p>
 
390
 
 
391
<pre class="py-listing">
 
392
class FingerService(service.Service):
 
393
    def __init__(self, filename):
 
394
        self.filename = filename
 
395
        self.update()
 
396
    def update(self):
 
397
        self.users = {}
 
398
        for line in file(self.filename):
 
399
            user, status = line[:-1].split(':', 1)
 
400
            self.users[user] = status
 
401
    def getUser(self, user):
 
402
        return defer.succeed(self.users.get(user, "No such user"))
 
403
    def getFingerFactory(self):
 
404
        f = protocol.ServerFactory()
 
405
        f.protocol, f.getUser = FingerProtocol, self.getUser
 
406
        return f
 
407
</pre>
 
408
 
 
409
<p>The TAC file:</p>
 
410
 
 
411
<pre class="py-listing">
 
412
application = service.Application('finger', uid=1, gid=1)
 
413
finger = FingerService('/etc/users')
 
414
server = internet.TCPServer(79, f.getFingerFactory())
 
415
periodic = internet.TimerService(30, f.update)
 
416
finger.setServiceParent(application)
 
417
server.setServiceParent(application)
 
418
periodic.setServiceParent(application)
 
419
</pre>
 
420
 
 
421
<p>Note how the actual periodic refreshing is a feature of the configuration, not the code. This is useful in the case we want to have other timers control refreshing, or perhaps even only refresh explicitly as depending on user action (another protocol, perhaps?).</p>
 
422
 
 
423
<h2>Writing Clients: A Finger Proxy</h2>
 
424
 
 
425
<p>It could be the case that our finger server needs to query another finger server, perhaps because of strange network configuration or maybe we just want to mask some users. Here is an example for a finger client, and a use case as a finger proxy. Note that in this example, we do not need custom services and so we do not develop them.</p>
 
426
 
 
427
<pre class="py-listing">
 
428
from twisted.internet import protocol, defer, reactor
 
429
class FingerClient(protocol.Protocol):
 
430
    buffer = ''
 
431
    def connectionMade(self):
 
432
        self.transport.write(self.factory.user+'\r\n')
 
433
    def dataReceived(self, data):
 
434
        self.buffer += data
 
435
    def connectionLost(self, reason):
 
436
        self.factory.gotResult(self.buffer)
 
437
 
 
438
class FingerClientFactory(protocol.ClientFactory):
 
439
    protocol = FingerClient
 
440
    def __init__(self, user):
 
441
        self.user = user 
 
442
        self.result = defer.Deferred()
 
443
    def gotResult(self, result):
 
444
        self.result.callback(result)
 
445
    def clientConnectionFailed(self, _, reason):
 
446
        self.result.errback(reason)
 
447
 
 
448
def query(host, port, user):
 
449
    f = FingerClientFactory(user)
 
450
    reactor.connectTCP(host, port, f)
 
451
    return f.result
 
452
 
 
453
class FingerProxyServer(protocol.ServerFactory):
 
454
    protocol = FingerProtocol
 
455
    def __init__(self, host, port=79):
 
456
        self.host, self.port = host, port
 
457
    def getUser(self, user):
 
458
        return query(self.host, self.port, user)
 
459
</pre>
 
460
 
 
461
<p>With a TAC that looks like:</p>
 
462
 
 
463
<pre class="py-listing">
 
464
application = service.Application('finger', uid=1, gid=1)
 
465
server = internet.TCPServer(79, FingerProxyFactory('internal.ibm.com'))
 
466
server.setServiceParent(application)
 
467
</pre>
 
468
 
 
469
<h2>What I Did Not Cover</h2>
 
470
 
 
471
<p>Twisted is large. Really large. Really really large. I could not hope to cover it all in thrice the time. What didn't I cover?</p>
 
472
 
 
473
<ul>
 
474
<li>Integration with GUI toolkits.</li>
 
475
<li>Nevow, a web-framework.</li>
 
476
<li>Twisted's internal remote call protocol, Perspective Broker.</li>
 
477
<li>Trial, a unit testing framework optimized for testing Twisted-based
 
478
    code.</li>
 
479
<li>cred, the user management framework.</li>
 
480
<li>Advanced deferred usage.</li>
 
481
<li>Threads abstraction.</li>
 
482
<li>Consumers/providers</li>
 
483
</ul>
 
484
 
 
485
<p>There is good documentation on the Twisted website, among which the tutorial which was based on an old HAIFUX talk and was, in turn, the basis for this talk, and specific HOWTOs for doing many useful and fun things.</p>
 
486
 
 
487
<h2>Notes on Non-Blocking</h2>
 
488
 
 
489
<p>In UNIX non-blocking has a very specific meaning -- some operations might block, others won't. Unfortunately, this meaning is almost completely useless in real life. Reading from a socket connected to a responsive server on a UNIX domain socket is blocking, while reading a megabyte string from a busy hard-drive is not. A more useful meaning for actual decisions while writing non-blocking code is <q>takes more than 0.05 seconds on my target platform</q>. With this kind of handlers, typical network usage will allow for the magical <q>one million hits a day</q> website, or a GUI application which appears to a human being as infinitely responsive. Various techniques, not limited but including threads, can be used to modify code to be responsive at those levels.</li>
 
490
 
 
491
<h2>Conclusion</h2>
 
492
 
 
493
<p>Twisted supports high-level abstractions for almost all levels of writing network code. Moreover, when using Twisted correctly it is possible to add more absractions, so that actual network applications do not have to fiddle with low-level protocol details. Developing network applications using Twisted and Python can lead to quick prototypes which can then be either optimized or rewritten on other platforms -- and often can just serve as-is.</p>
 
494
 
 
495
</body></html>