Initial import to Tizen
[profile/ivi/python-twisted.git] / doc / historic / 2003 / europython / twisted.html
1 <html><head><title>Twisted Tutorial</title></head>
2 <body>
3
4 <h1>Twisted Tutorial</h1>
5
6 <h2>Twisted -- The Tutorial</h2><ul>
7 <li>Welcome</li>
8
9 <li>Gimmick -- Charmed quotes</li>
10
11 </ul>
12 <hr />
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>
16
17 <li>Handles the icky select stuff</li>
18
19 <li>No threads, no blocking</li>
20
21 </ul>
22 <hr />
23 <em>Leo (Bite Me, season 4) -- As far as I know they're apart of a whole different network now.</em>
24 <h2>Finger</h2><ul>
25 <li>Send username</li>
26
27 <li>Get back some stuff about user</li>
28
29 <li>Will only implement subset of protocol here</li>
30
31 </ul>
32 <hr />
33 <em>Natalie (Blinded By the Whitelighter) -- I'll assume a demon attacked your finger</em>
34 <h2>Finger - Protocol code</h2>
35 <pre class="python">
36 from twisted.protocols import basic
37
38 class FingerClient(basic.LineReceiver):
39
40     # This will be called when the connection is made
41     def connectionMade(self): self.sendLine(self.factory.user)
42
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
47
48     # This will be called when the connection is terminated
49     def connectionLost(self, _): print "-"*40
50 </pre>
51 <hr />
52 <em>Phoebe (Blind Sided, season 1) -- Standard dating protocol.</em>
53 <h2>Finger - client factory</h2><ul>
54 <li>Keep configuration information</li>
55
56 <li>In this case, just the username</li></ul>
57
58 <pre class="python">
59 from twisted.internet import protocol
60
61 class FingerFactory(protocol.ClientFactory):
62     protocol = FingerProtocol
63
64     def __init__(self, user): self.user = user
65
66     def clientConnectionFailed(self, _, reason):
67         print "error", reason.value
68 </pre>
69
70 <hr />
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>
74
75 <li>Use reactors</li></ul>
76
77 <pre class="python">
78 from twisted.internet import reactor
79 import sys
80
81 user, host = sys.argv[1].split('@')
82 port = 79
83 reactor.connectTCP(host, port, FingerFactory(port))
84 reactor.run()
85 </pre>
86 <hr />
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>
90
91 <li>Reactor continues in a loop</li>
92
93 <li>Takes almost no CPU time...</li>
94
95 <li>...but still wrong behaviour</li>
96
97 </ul>
98 <hr />
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>
102
103 <li>Common callbacks are too weak</li>
104
105 <li>We used 'deferreds' as an abstraction for callbacks</li>
106
107 </ul>
108 <hr />
109 <em>Piper (Morality Bites, season 2) -- Talk about it later.</em>
110 <h2>Finger - reimplementing correctly</h2>
111 <pre class="python">
112 from twisted.protocols import basic
113 from twisted.internet import protocol, defer
114 import sys
115
116 class FingerClient(basic.LineReceiver):
117
118     def connectionMade(self):
119         self.transport.write(self.factory.user+"\n")
120
121     def lineReceived(self, line):
122         self.factory.gotLine(line)
123 </pre>
124
125
126 <h2>Finger - reimplementing correctly (cont'd)</h2>
127 <pre class="python">
128 class FingerFactory(protocol.ClientFactory):
129     protocol = FingerProtocol
130
131     def __init__(self, user):
132         self.user, self.d = user, defer.Deferred()
133
134     def gotLine(self, line): print line
135
136     def clientConnectionLost(self, _, why): self.d.callback(None)
137
138     def clientConnectionFailed(self, _, why): self.d.errback(why)
139 </pre>
140
141 <h2>Finger - reimplementing correctly (cont'd 2)</h2>
142 <pre class="python">
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)
148     port = 79
149     reactor.connectTCP(host, port, FingerFactory(port))
150     f.d.addCallback(lambda _: reactor.stop())
151     f.d.addErrback(lambda _: (util.println("could not connect"),
152                               reactor.stop()))
153     reactor.run()
154 </pre>
155 <hr />
156 <em>Phoebe (Charmed and Dangerous, season 4) -- That's what we were missing.</em>
157 <h2>Servers</h2><ul>
158 <li>Servers are actually easier</li>
159
160 <li>Servers meant to wait for events</li>
161
162 <li>Most of concepts similar to clients</li>
163
164 </ul>
165 <hr />
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>
168 <pre class="python">
169 class FingerServer(basic.LineReceiver):
170
171     def lineReceived(self, line):
172         self.transport.write(self.factory.getUser(line))
173         self.transport.loseConnection()
174 </pre>
175 <hr />
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>
178 <pre class="python">
179 class FingerServerFactory(protocol.Factory):
180
181     protocol = FingerServer
182
183     def __init__(self):
184         self.users = {}
185         self.message = "No such user\n"
186
187     def getUser(self, name):
188         return self.users.get(name, self.message)
189
190     def setUser(self, user, status):
191         self.users[user] = status
192 </pre>
193 <hr />
194 <em>Prue (The Demon Who Came In From the Cole, season 3) -- Okay, so who are they?</em>
195 <h2>Finger - glue</h2>
196 <pre class="python">
197 factory = FingerServerFactory()
198 factory.setUser("moshez", "Online - Sitting at computer\n")
199 factory.setUser("spiv", "Offline - Surfing the waves\n")
200
201 reactor.listenTCP(79, factory)
202 </pre>
203 <hr />
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>
207
208 <li>For example, read status from a website</li>
209
210 <li>API forces us to block -- not good</li>
211
212 </ul>
213 <hr />
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>
216 <pre class="python">
217 class FingerServer(basic.LineReceiver):
218
219     def lineReceived(self, line):
220         d = self.factory.getUser(line)
221         d.addCallback(self.writeResponse)
222         d.addErrback(self.writeError)
223
224     def writeResponse(self, response):
225         self.transport.write(response)
226         self.transport.loseConnection()
227
228     def writeError(self, error):
229         self.transport.write("Server error -- try later\n")
230         self.transport.loseConnection()
231 </pre>
232 <hr />
233 <em>Piper (Ex Libris, season 2) -- We'll worry about it later.</em>
234 <h2>Finger - factory</h2>
235 <pre class="python">
236 class FingerServerFactory(protocol.Factory):
237
238     protocol = FingerServer
239
240     def __init__(self):
241         self.users = {}
242         self.message = "No such user\n"
243
244     def getUser(self, name):
245         return defer.succeed(self.users.get(name, self.message))
246
247     def setUser(self, user, status):
248         self.users[user] = status
249 </pre>
250 <hr />
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>
253 <pre class="python">
254 from twisted.web import client
255
256 class FingerWebFactory(protocol.Factory):
257     protocol = FingerServer
258
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")
263         return d
264 </pre>
265 <hr />
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>
269
270 <li>Decouple configuration from running</li></ul>
271
272 <h2>Application (Example)</h2>
273 <pre class="python">
274 # File: finger.tpy
275 from twisted.internet import app
276 import fingerserver
277
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)
283 </pre>
284
285 <hr />
286 <em>Paige (Hell Hath No Fury, season 4) -- I am taking full responsibility for being late with the application.</em>
287 <h2>twistd</h2><ul>
288 <li>TWISTed Daemonizer</li>
289
290 <li>Daemonizes Twisted servers</li>
291
292 <li>Takes care of log files, PID files, etc.</li>
293
294 <li>twistd -y finger.tpy</li>
295
296 </ul>
297 <hr />
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>
301
302 <li>twistd -y finger.tpy --pidfile /var/run/finger.pid</li>
303
304 <li>twistd -y finger.tpy --chroot /var/run</li>
305
306 </ul>
307 <hr />
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>
311
312 <li>Accessible via commandline or GUI</li></ul>
313
314 <h2>Writing Plugins (Example)</h2>
315 <pre class="python">
316 # File finger/tap.py
317 from twisted.python import usage
318
319 class Options(usage.Options):
320     synopsis = "Usage: mktap finger [options]"
321     optParameters = [["port", "p", 6666,"Set the port number."]]
322     longdesc = 'Finger Server'
323     users = ()
324
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"),)
329 </pre>
330
331         
332 <h2>Writing Plugins (Example cont'd)</h2>
333 <pre class="python">
334 def updateApplication(app, config):
335     f = FingerFactory()
336     for (user, status) in config.users:
337         f.setUser(user, status)
338     app.listenTCP(int(config.opts['port']), s)
339 </pre>
340 <hr />
341 <em>Paige (Bite Me, season 4) -- They won't join us willingly.</em>
342 <h2>Writing Plugins (Example cont'd 2)</h2>
343 <pre class="python">
344 # File finger/plugins.tml
345 register("Finger",
346          "finger.tap",
347          description="Finger Server",
348          type='tap',
349          tapname="finger")
350 </pre>
351 <hr />
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>
355
356 <li>twistd -f finger.tap</li>
357
358 </ul>
359 <hr />
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(&lt;time&gt;, &lt;function&gt;, [&lt;arg&gt;, [&lt;arg&gt; ...]])</li>
363
364 <li>reactor.callLater(10, reactor.stop)</li>
365
366 <li>reactor.callLater(5, util.println, 'hello', 'world')</li>
367
368 </ul>
369 <hr />
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>
373
374 <li>Can be used in algorithm-heavy code to let other code run</li></ul>
375
376 <pre class="python">
377 def calculateFact(cur, acc=1, d=None):
378     d = d or defer.Deferred()
379     if cur&lt;=1: d.callback(acc)
380     else: reactor.callLater(0, calculateFact, acc*cur, cur-1, d)
381
382 calculateFact(10
383 ).addCallback(lambda n: (util.println(n), reactor.stop()))
384 reactor.run()
385 </pre>
386 <hr />
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>
390 </ul></li>
391
392 <li>Clients<ul><li>reactor.connectUNIX('/var/run/finger.sock', FingerFactory())</li>
393 </ul></li>
394
395 </ul>
396 <hr />
397 <em>Kate (Once Upon a Time, season 3) -- Fairies don't talk the same way people do.</em>
398 <h2>SSL Servers</h2>
399
400 <pre class="python">
401 from OpenSSL import SSL
402
403 class ServerContextFactory:
404
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')
409         return ctx
410
411 reactor.listenSSL(111, FingerWebFactory(), ServerContextFactory())
412 </pre>
413
414 <h2>SSL Clients</h2>
415
416 <ul>
417 <li>from twisted.internet import ssl</li>
418
419 <li>reactor.connectSSL(111, 'localhost', FingerFactory(), ssl.ClientContextFactory())</li>
420 </ul>
421 <hr />
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>
425
426 <li>Protocol to interface with it is different</li></ul>
427
428 <pre class="python">
429 class Advertizer(protocol.ProcessProtocol):
430     def outReceived(self, data): print "out", `data`
431
432     def errReceived(self, data): print "error", `data`
433
434     def processEnded(self, reason): print "ended", reason
435
436 reactor.spawnProcess(Advertizer(),
437                      "echo", ["echo", "hello", "world"])
438 </pre>
439 <hr />
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>
443
444 </ul>
445 <hr />
446 <em>Phoebe (Animal Pragmatism, season 2) -- Ooh, the girls in school are reading this.</em>
447 <h2>Questions?</h2>
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>
453
454 <li>Can transfer references or copies</li>
455
456 <li>Secure (no pickles or other remote execution mechanisms)</li>
457
458 <li>Lightweight (bandwidth and CPU)</li>
459
460 <li>Translucent</li>
461
462 </ul>
463 <hr />
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>
466 <pre class="python">
467 from twisted.spread import pb
468
469 class FingerSetter(pb.Root):
470
471     def __init__(self, ff): self.ff = ff
472
473     def remote_setUser(self, name, status):
474         self.ff.setUser(name, status+"\n")
475
476 ff = FingerServerFactory()
477 setter = FingerSetter(ff)
478 reactor.listenUNIX("/var/run/finger.control",
479                   pb.BrokerFactory(setter))
480 </pre>
481 <hr />
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>
484 <pre class="python">
485 from twisted.spread import pb
486 from twisted.internet import reactor
487 import sys
488
489 def failed(reason):
490     print "failed:", reason.value;reactor.stop()
491
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)
495
496 reactor.run()
497 </pre>
498 <hr />
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>
502
503 <li>Portable (works on Windows)</li>
504
505 <li>Gets around OS security limitations</li>
506
507 <li>Need to add application-level security</li>
508
509 <li>The docs have the answers (see 'cred')</li>
510
511 </ul>
512 <hr />
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>
515 <li>pb.cred</li>
516
517 <li>Perspectives</li>
518
519 <li>Can get remote user with every call<ul><li>Inherit from pb.Perpsective</li>
520
521 <li>Call methods perspective_&lt;name&gt;(self, remoteUser, ...)</li>
522 </ul></li>
523
524 </ul>
525 <hr />
526 <em>Piper (She's a Man, Baby, a Man!, season 2) -- Okey-Dokey. I get the point.</em>
527
528 <h2>Perspective Broker - About Large Data Streams</h2>
529
530 <ul>
531
532 <li>Sending large (>640kb) strings is impossible -- feature, not bug.</li>
533
534 <li>It stops DoSes</li>
535
536 <li>Nobody would ever need...<ul><li>JokeTooOldError</li></ul></li>
537
538 <li>Use twisted.spread.utils.Pager -- sends the data in managable chunks.</li>
539
540 </ul>
541
542 <hr />
543 <em>Piper (Womb Raider, season 4) --
544 Oral tradition tales of a giant whose body served as a portal to other
545 dimensions.</em>
546
547 <h2>Producers and Consumers</h2><ul>
548 <li>Use for things like sending a big file</li>
549
550 <li>A good alternative to manually reactor.callLater(0,)-ing</li>
551
552 <li>See twisted.internet.interfaces.{IProducer,IConsumer}</li>
553
554 </ul>
555 <hr />
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>
559
560 <li>Use for blocking calls you can't do without</li>
561
562 <li>deferred = reactor.callInThread(function, arg, arg)</li>
563
564 </ul>
565 <hr />
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>
569
570 <li>Always thread safe</li>
571
572 <li>Interface to non-thread-safe APIs</li>
573
574 <li>reactor.callFromThread(protocol.transport.write, s)</li>
575
576 </ul>
577 <hr />
578 <em>Phoebe (Witch Trial, season 2) -- Maybe it's still in the house. Just on different plane.</em>
579
580 <h2>Using ApplicationService</h2><ul>
581 <li>Keep useful data...</li>
582
583 <li>...or useful volatile objects</li>
584
585 <li>Support start/stop notification</li>
586
587 <li>Example: process monitor</li>
588
589 </ul>
590 <hr />
591 <em>Phoebe (Marry Go Round, season 4) -- Yeah, that's just in case you need psychic services.</em>
592
593 <h2>Playing With Persistence</h2><ul>
594 <li>Shutdown taps are useful</li>
595
596 <li>Even if you use twistd -y</li>
597
598 <li>So remember<ul><li>Classes belong in modules</li>
599
600 <li>Functions belong in modules</li>
601
602 <li>Modifying class attributes should be avoided</li>
603 </ul></li>
604
605 </ul>
606 <hr />
607 <em>Cole (Marry Go Round, season 4) -- That Lazerus demon is a time bomb waiting to explode</em>
608 </body></html>