1 <html><head><title>Twisted Tutorial</title></head>
4 <h1>Twisted Tutorial</h1>
6 <h2>Twisted -- The Tutorial</h2><ul>
9 <li>Gimmick -- Charmed quotes</li>
13 <em>Prue (Something Wicca This Way Comes, season 1) -- Piper, the girl has no vision, no sense of the future.</em>
14 <h2>Twisted -- Networking For Python</h2><ul>
15 <li>Handles the icky socket stuff</li>
17 <li>Handles the icky select stuff</li>
19 <li>No threads, no blocking</li>
23 <em>Leo (Bite Me, season 4) -- As far as I know they're apart of a whole different network now.</em>
25 <li>Send username</li>
27 <li>Get back some stuff about user</li>
29 <li>Will only implement subset of protocol here</li>
33 <em>Natalie (Blinded By the Whitelighter) -- I'll assume a demon attacked your finger</em>
34 <h2>Finger - Protocol code</h2>
36 from twisted.protocols import basic
38 class FingerClient(basic.LineReceiver):
40 # This will be called when the connection is made
41 def connectionMade(self): self.sendLine(self.factory.user)
43 # This will be called when the server sends us a line.
44 # IMPORTANT: line *without "\n" at end.
45 # Yes, this means empty line does not mean EOF
46 def lineReceived(self, line): print line
48 # This will be called when the connection is terminated
49 def connectionLost(self, _): print "-"*40
52 <em>Phoebe (Blind Sided, season 1) -- Standard dating protocol.</em>
53 <h2>Finger - client factory</h2><ul>
54 <li>Keep configuration information</li>
56 <li>In this case, just the username</li></ul>
59 from twisted.internet import protocol
61 class FingerFactory(protocol.ClientFactory):
62 protocol = FingerProtocol
64 def __init__(self, user): self.user = user
66 def clientConnectionFailed(self, _, reason):
67 print "error", reason.value
71 <em>Jack (Ms. Hellfire, season 2) -- Well, they'd better be a rich client</em>
72 <h2>Finger - tying it all together</h2><ul>
73 <li>Actually run above code</li>
75 <li>Use reactors</li></ul>
78 from twisted.internet import reactor
81 user, host = sys.argv[1].split('@')
83 reactor.connectTCP(host, port, FingerFactory(port))
87 <em>Prue/Phoebe/Piper (Something Wicca This Way Comes, season 1) -- The power of three will set us free</em>
88 <h2>Finger - a bug</h2><ul>
89 <li>Succeed or fail, program doesn't exit</li>
91 <li>Reactor continues in a loop</li>
93 <li>Takes almost no CPU time...</li>
95 <li>...but still wrong behaviour</li>
99 <em>Leo (Trial By Magic, season 4) -- Demons you can handle but not rats?</em>
100 <h2>Digression - Deferreds</h2><ul>
101 <li>In order to be more flexible, we want callbacks</li>
103 <li>Common callbacks are too weak</li>
105 <li>We used 'deferreds' as an abstraction for callbacks</li>
109 <em>Piper (Morality Bites, season 2) -- Talk about it later.</em>
110 <h2>Finger - reimplementing correctly</h2>
112 from twisted.protocols import basic
113 from twisted.internet import protocol, defer
116 class FingerClient(basic.LineReceiver):
118 def connectionMade(self):
119 self.transport.write(self.factory.user+"\n")
121 def lineReceived(self, line):
122 self.factory.gotLine(line)
126 <h2>Finger - reimplementing correctly (cont'd)</h2>
128 class FingerFactory(protocol.ClientFactory):
129 protocol = FingerProtocol
131 def __init__(self, user):
132 self.user, self.d = user, defer.Deferred()
134 def gotLine(self, line): print line
136 def clientConnectionLost(self, _, why): self.d.callback(None)
138 def clientConnectionFailed(self, _, why): self.d.errback(why)
141 <h2>Finger - reimplementing correctly (cont'd 2)</h2>
143 if __name__ == '__main__':
144 from twisted.internet import reactor
145 from twisted.python import util
146 user, host = sys.argv[1].split('@')
147 f = FingerFactory(user)
149 reactor.connectTCP(host, port, FingerFactory(port))
150 f.d.addCallback(lambda _: reactor.stop())
151 f.d.addErrback(lambda _: (util.println("could not connect"),
156 <em>Phoebe (Charmed and Dangerous, season 4) -- That's what we were missing.</em>
158 <li>Servers are actually easier</li>
160 <li>Servers meant to wait for events</li>
162 <li>Most of concepts similar to clients</li>
166 <em>Genie (Be Careful What You Witch For, season 2) -- All I know is that you rubbed and now I serve.</em>
167 <h2>Finger - protocol</h2>
169 class FingerServer(basic.LineReceiver):
171 def lineReceived(self, line):
172 self.transport.write(self.factory.getUser(line))
173 self.transport.loseConnection()
176 <em>Secretary (The Painted World, season 2) -- Well, you won't have any trouble with this if you figured that out.</em>
177 <h2>Finger - factory</h2>
179 class FingerServerFactory(protocol.Factory):
181 protocol = FingerServer
185 self.message = "No such user\n"
187 def getUser(self, name):
188 return self.users.get(name, self.message)
190 def setUser(self, user, status):
191 self.users[user] = status
194 <em>Prue (The Demon Who Came In From the Cole, season 3) -- Okay, so who are they?</em>
195 <h2>Finger - glue</h2>
197 factory = FingerServerFactory()
198 factory.setUser("moshez", "Online - Sitting at computer\n")
199 factory.setUser("spiv", "Offline - Surfing the waves\n")
201 reactor.listenTCP(79, factory)
204 <em>Prue (All Halliwell's Eve, season 3) -- Put it all together, it may just work.</em>
205 <h2>Finger Server - problem</h2><ul>
206 <li>What if server has to actually work to find user's status?</li>
208 <li>For example, read status from a website</li>
210 <li>API forces us to block -- not good</li>
214 <em>Piper (All Halliwell's Eve, season 3) -- We've got big problems, a little time and a little magic.</em>
215 <h2>Finger server -- new protocol</h2>
217 class FingerServer(basic.LineReceiver):
219 def lineReceived(self, line):
220 d = self.factory.getUser(line)
221 d.addCallback(self.writeResponse)
222 d.addErrback(self.writeError)
224 def writeResponse(self, response):
225 self.transport.write(response)
226 self.transport.loseConnection()
228 def writeError(self, error):
229 self.transport.write("Server error -- try later\n")
230 self.transport.loseConnection()
233 <em>Piper (Ex Libris, season 2) -- We'll worry about it later.</em>
234 <h2>Finger - factory</h2>
236 class FingerServerFactory(protocol.Factory):
238 protocol = FingerServer
242 self.message = "No such user\n"
244 def getUser(self, name):
245 return defer.succeed(self.users.get(name, self.message))
247 def setUser(self, user, status):
248 self.users[user] = status
251 <em>Piper/Zen Master (Enter the Demon, season 4) -- It is a different realm down there with new rules.</em>
252 <h2>Finger - web factory</h2>
254 from twisted.web import client
256 class FingerWebFactory(protocol.Factory):
257 protocol = FingerServer
259 def getUser(self, name):
260 url = "http://example.com/~%s/online" % name
261 d = client.getPage(url)
262 d.addErrback(lambda _: "No such user\n")
266 <em>Applicant #3 (The Painted World, season 2) -- in this day and age, who can't write in the HTML numeric languages, right?</em>
267 <h2>Application</h2><ul>
268 <li>The Twisted way of configuration files</li>
270 <li>Decouple configuration from running</li></ul>
272 <h2>Application (Example)</h2>
275 from twisted.internet import app
278 factory = fingerserver.FingerServerFactory()
279 factory.setUser("moshez", "Online - Sitting at computer\n")
280 factory.setUser("spiv", "Offline - Surfing the waves\n")
281 application = app.Application("finger")
282 application.listenTCP(79, factory)
286 <em>Paige (Hell Hath No Fury, season 4) -- I am taking full responsibility for being late with the application.</em>
288 <li>TWISTed Daemonizer</li>
290 <li>Daemonizes Twisted servers</li>
292 <li>Takes care of log files, PID files, etc.</li>
294 <li>twistd -y finger.tpy</li>
298 <em>Phoebe (Sleuthing With the Enemy, season 3) -- Was it some sick twisted demonic thrill?</em>
299 <h2>twistd examples</h2><ul>
300 <li>twistd -y finger.tpy -l /var/finger/log</li>
302 <li>twistd -y finger.tpy --pidfile /var/run/finger.pid</li>
304 <li>twistd -y finger.tpy --chroot /var/run</li>
308 <em>Professor Whittlessy (Is There a Woogy In the House?, season 1) -- I use your house as an example</em>
309 <h2>Writing Plugins</h2><ul>
310 <li>Automatically create application configurations</li>
312 <li>Accessible via commandline or GUI</li></ul>
314 <h2>Writing Plugins (Example)</h2>
317 from twisted.python import usage
319 class Options(usage.Options):
320 synopsis = "Usage: mktap finger [options]"
321 optParameters = [["port", "p", 6666,"Set the port number."]]
322 longdesc = 'Finger Server'
325 def opt_user(self, user):
326 if not '=' in user: status = "Online"
327 else: user, status = user.split('=', 1)
328 self.users += ((user, status+"\n"),)
332 <h2>Writing Plugins (Example cont'd)</h2>
334 def updateApplication(app, config):
336 for (user, status) in config.users:
337 f.setUser(user, status)
338 app.listenTCP(int(config.opts['port']), s)
341 <em>Paige (Bite Me, season 4) -- They won't join us willingly.</em>
342 <h2>Writing Plugins (Example cont'd 2)</h2>
344 # File finger/plugins.tml
347 description="Finger Server",
352 <em>Queen (Bite Me, season 4) -- That's what families are for.</em>
353 <h2>Using mktap</h2><ul>
354 <li>mktap finger --user moshez --user spiv=Offline</li>
356 <li>twistd -f finger.tap</li>
360 <em>Piper (Charmed and Dangerous, season 4) -- We'll use potions instead.</em>
361 <h2>Delayed execution</h2><ul>
362 <li>Basic interface: reactor.callLater(<time>, <function>, [<arg>, [<arg> ...]])</li>
364 <li>reactor.callLater(10, reactor.stop)</li>
366 <li>reactor.callLater(5, util.println, 'hello', 'world')</li>
370 <em>Cole (Enter the Demon, season 4) -- I know, but not right now.</em>
371 <h2>callLater(0,) -- An idiom</h2><ul>
372 <li>Use to set up a call in next iteration of loop</li>
374 <li>Can be used in algorithm-heavy code to let other code run</li></ul>
377 def calculateFact(cur, acc=1, d=None):
378 d = d or defer.Deferred()
379 if cur<=1: d.callback(acc)
380 else: reactor.callLater(0, calculateFact, acc*cur, cur-1, d)
383 ).addCallback(lambda n: (util.println(n), reactor.stop()))
387 <em>Piper (Lost and Bound, season 4) -- Someone, I won't say who, has the insane notion</em>
388 <h2>UNIX Domain Sockets</h2><ul>
389 <li>Servers<ul><li>reactor.listenUNIX('/var/run/finger.sock', FingerWebFactory())</li>
392 <li>Clients<ul><li>reactor.connectUNIX('/var/run/finger.sock', FingerFactory())</li>
397 <em>Kate (Once Upon a Time, season 3) -- Fairies don't talk the same way people do.</em>
401 from OpenSSL import SSL
403 class ServerContextFactory:
405 def getContext(self):
406 ctx = SSL.Context(SSL.SSLv23_METHOD)
407 ctx.use_certificate_file('server.pem')
408 ctx.use_privatekey_file('server.pem')
411 reactor.listenSSL(111, FingerWebFactory(), ServerContextFactory())
417 <li>from twisted.internet import ssl</li>
419 <li>reactor.connectSSL(111, 'localhost', FingerFactory(), ssl.ClientContextFactory())</li>
422 <em>Natalie (Blinded By the Whitelighter, season 3) -- I mean, in private if you wouldn't mind</em>
423 <h2>Running Processes</h2><ul>
424 <li>A process has two outputs: stdout and stderr</li>
426 <li>Protocol to interface with it is different</li></ul>
429 class Advertizer(protocol.ProcessProtocol):
430 def outReceived(self, data): print "out", `data`
432 def errReceived(self, data): print "error", `data`
434 def processEnded(self, reason): print "ended", reason
436 reactor.spawnProcess(Advertizer(),
437 "echo", ["echo", "hello", "world"])
440 <em>Prue (Coyote Piper, season 3) -- You have to know that you can talk to me</em>
441 <h2>Further Reading</h2><ul>
442 <li><a href="http://twistedmatrix.com/documents/">Twisted Docs</a></li>
446 <em>Phoebe (Animal Pragmatism, season 2) -- Ooh, the girls in school are reading this.</em>
448 <em>Piper (Something Wicca This Way Comes, season 1) -- Tell me that's not our old spirit board?</em>
449 <h2>Bonus Slides</h2>
450 <em>Prue (Sleuthing With the Enemy, season 3) -- All right, you start talking or we start the bonus round.</em>
451 <h2>Perspective Broker</h2><ul>
452 <li>Meant to be worked async</li>
454 <li>Can transfer references or copies</li>
456 <li>Secure (no pickles or other remote execution mechanisms)</li>
458 <li>Lightweight (bandwidth and CPU)</li>
464 <em>Paige (Charmed Again, season 4) -- I guess I just kind of feel - connected somehow.</em>
465 <h2>PB Remote Control Finger (Server)</h2>
467 from twisted.spread import pb
469 class FingerSetter(pb.Root):
471 def __init__(self, ff): self.ff = ff
473 def remote_setUser(self, name, status):
474 self.ff.setUser(name, status+"\n")
476 ff = FingerServerFactory()
477 setter = FingerSetter(ff)
478 reactor.listenUNIX("/var/run/finger.control",
479 pb.BrokerFactory(setter))
482 <em>Piper (Be Careful What You Witch For, season 2) -- Okay, you think you can control the power this time?</em>
483 <h2>PB Remote Control Finger (Client)</h2>
485 from twisted.spread import pb
486 from twisted.internet import reactor
490 print "failed:", reason.value;reactor.stop()
492 pb.getObjectAt("unix", "/var/run/finger.control", 30
493 ).addCallback(lambda o: o.callRemote("setUser", *sys.argv[1:3],
494 ).addCallbacks(lambda _: reactor.stop(), failed)
499 <em>Leo (Be Careful What You Witch For, season 2) -- How about you just keep your arms down until you learn how to work the controls.</em>
500 <h2>Perspective Broker (Trick)</h2><ul>
501 <li>Add to the application something which will call reactor.stop()</li>
503 <li>Portable (works on Windows)</li>
505 <li>Gets around OS security limitations</li>
507 <li>Need to add application-level security</li>
509 <li>The docs have the answers (see 'cred')</li>
513 <em>Piper (Lost and Bound, season 4) -- They're not good or bad by themselves, it's how we use them</em>
514 <h2>Perspective Broker (Authentication)</h2><ul>
517 <li>Perspectives</li>
519 <li>Can get remote user with every call<ul><li>Inherit from pb.Perpsective</li>
521 <li>Call methods perspective_<name>(self, remoteUser, ...)</li>
526 <em>Piper (She's a Man, Baby, a Man!, season 2) -- Okey-Dokey. I get the point.</em>
528 <h2>Perspective Broker - About Large Data Streams</h2>
532 <li>Sending large (>640kb) strings is impossible -- feature, not bug.</li>
534 <li>It stops DoSes</li>
536 <li>Nobody would ever need...<ul><li>JokeTooOldError</li></ul></li>
538 <li>Use twisted.spread.utils.Pager -- sends the data in managable chunks.</li>
543 <em>Piper (Womb Raider, season 4) --
544 Oral tradition tales of a giant whose body served as a portal to other
547 <h2>Producers and Consumers</h2><ul>
548 <li>Use for things like sending a big file</li>
550 <li>A good alternative to manually reactor.callLater(0,)-ing</li>
552 <li>See twisted.internet.interfaces.{IProducer,IConsumer}</li>
556 <em>Phoebe (Black as Cole, season 4) -- Apparently he feeds on the remains of other demons' victims.</em>
557 <h2>Threads (callInThread)</h2><ul>
558 <li>Use for long running calculations</li>
560 <li>Use for blocking calls you can't do without</li>
562 <li>deferred = reactor.callInThread(function, arg, arg)</li>
566 <em>Piper (The Painted World, season 2) -- There will be consequences. There always are.</em>
567 <h2>Threads (callFromThread)</h2><ul>
568 <li>Use from a function running in a different thread</li>
570 <li>Always thread safe</li>
572 <li>Interface to non-thread-safe APIs</li>
574 <li>reactor.callFromThread(protocol.transport.write, s)</li>
578 <em>Phoebe (Witch Trial, season 2) -- Maybe it's still in the house. Just on different plane.</em>
580 <h2>Using ApplicationService</h2><ul>
581 <li>Keep useful data...</li>
583 <li>...or useful volatile objects</li>
585 <li>Support start/stop notification</li>
587 <li>Example: process monitor</li>
591 <em>Phoebe (Marry Go Round, season 4) -- Yeah, that's just in case you need psychic services.</em>
593 <h2>Playing With Persistence</h2><ul>
594 <li>Shutdown taps are useful</li>
596 <li>Even if you use twistd -y</li>
598 <li>So remember<ul><li>Classes belong in modules</li>
600 <li>Functions belong in modules</li>
602 <li>Modifying class attributes should be avoided</li>
607 <em>Cole (Marry Go Round, season 4) -- That Lazerus demon is a time bomb waiting to explode</em>