Initial import to Tizen
[profile/ivi/python-twisted.git] / doc / historic / 2003 / haifux / haifux.html
1 <html><head><title>Evolution of Finger</title></head><body>
2 <h1>Evolution of Finger</h1>
3
4 <h2>Refuse Connections</h2>
5
6 <pre>
7 from twisted.internet import reactor
8 reactor.run()
9 </pre>
10
11 <p>Here, we just run the reactor. Nothing at all will happen,
12 until we interrupt the program. It will not consume (almost)
13 no CPU resources. Not very useful, perhaps -- but this
14 is the skeleton inside which the Twisted program
15 will grow.</p>
16
17 <h2>Do Nothing</h2>
18
19 <pre>
20 from twisted.internet import protocol, reactor
21 class FingerProtocol(protocol.Protocol):
22     pass
23 class FingerFactory(protocol.ServerFactory):
24     protocol = FingerProtocol
25 reactor.listenTCP(1079, FingerFactory())
26 reactor.run()
27 </pre>
28
29 <p>Here, we start listening on port 1079 [which is supposed to be
30 a reminder that eventually, we want to run on port 79, the port
31 the finger server is supposed to run on. We define a protocol which
32 does not respond to any events. Thus, connections to 1079 will
33 be accepted, but the input ignored.</p>
34
35 <h2>Drop Connections</h2>
36
37 <pre>
38 from twisted.internet import protocol, reactor
39 class FingerProtocol(protocol.Protocol):
40     def connectionMade(self):
41         self.transport.loseConnection()
42 class FingerFactory(protocol.ServerFactory):
43     protocol = FingerProtocol
44 reactor.listenTCP(1079, FingerFactory())
45 reactor.run()
46 </pre>
47
48 <p>Here we add to the protocol the ability to respond to the
49 event of beginning a connection -- by terminating it.
50 Perhaps not an interesting behaviour, but it is already
51 not that far from behaving according to the letter of the
52 protocol. After all, there is no requirement to send any
53 data to the remote connection in the standard, is there.
54 The only technical problem is that we terminate the connection
55 too soon. A client which is slow enough will see his send()
56 of the username result in an error.</p>
57
58 <h2>Read Username, Drop Connections</h2>
59
60 <pre>
61 from twisted.internet import protocol, reactor
62 from twisted.protocols import basic
63 class FingerProtocol(basic.LineReceiver):
64     def lineReceived(self, user):
65         self.transport.loseConnection()
66 class FingerFactory(protocol.ServerFactory):
67     protocol = FingerProtocol
68 reactor.listenTCP(1079, FingerFactory())
69 reactor.run()
70 </pre>
71
72 <p>Here we make <code>FingerProtocol</code> inherit from
73 <code>LineReceiver</code>, so that we get data-based events
74 on a line-by-line basis. We respond to the event of receiving
75 the line with shutting down the connection. Congratulations,
76 this is the first standard-compliant version of the code.
77 However, usually people actually expect some data about
78 users to be transmitted.</p>
79
80
81 <h2>Read Username, Output Error, Drop Connections</h2>
82
83 <pre>
84 from twisted.internet import protocol, reactor
85 from twisted.protocols import basic
86 class FingerProtocol(basic.LineReceiver):
87     def lineReceived(self, user):
88         self.transport.write("No such user\r\n")
89         self.transport.loseConnection()
90 class FingerFactory(protocol.ServerFactory):
91     protocol = FingerProtocol
92 reactor.listenTCP(1079, FingerFactory())
93 reactor.run()
94 </pre>
95
96 <p>Finally, a useful version. Granted, the usefulness is somewhat
97 limited by the fact that this version only prints out a no such
98 user message. It could be used for devestating effect in honeypots,
99 of course :)</p>
100
101 <h2>Output From Empty Factory</h2>
102
103 <pre>
104 # Read username, output from empty factory, drop connections
105 from twisted.internet import protocol, reactor
106 from twisted.protocols import basic
107 class FingerProtocol(basic.LineReceiver):
108     def lineReceived(self, user):
109         self.transport.write(self.factory.getUser(user)+"\r\n")
110         self.transport.loseConnection()
111 class FingerFactory(protocol.ServerFactory):
112     protocol = FingerProtocol
113     def getUser(self, user): return "No such user"
114 reactor.listenTCP(1079, FingerFactory())
115 reactor.run()
116 </pre>
117
118 <p>The same behaviour, but finally we see what usefuleness the
119 factory has: as something that does not get constructed for
120 every connection, it can be in charge of the user database.
121 In particular, we won't have to change the protocol if
122 the user database backend changes.</p>
123
124 <h2>Output from Non-empty Factory</h2>
125
126 <pre>
127 # Read username, output from non-empty factory, drop connections
128 from twisted.internet import protocol, reactor
129 from twisted.protocols import basic
130 class FingerProtocol(basic.LineReceiver):
131     def lineReceived(self, user):
132         self.transport.write(self.factory.getUser(user)+"\r\n")
133         self.transport.loseConnection()
134 class FingerFactory(protocol.ServerFactory):
135     protocol = FingerProtocol
136     def __init__(self, **kwargs): self.users = kwargs
137     def getUser(self, user):
138         return self.users.get(user, "No such user")
139 reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
140 reactor.run()
141 </pre>
142
143 <p>Finally, a really useful finger database. While it does not
144 supply information about logged in users, it could be used to
145 distribute things like office locations and internal office
146 numbers. As hinted above, the factory is in charge of keeping
147 the user database: note that the protocol instance has not
148 changed. This is starting to look good: we really won't have
149 to keep tweaking our protocol.</p>
150
151 <h2>Use Deferreds</h2>
152
153 <pre>
154 # Read username, output from non-empty factory, drop connections
155 # Use deferreds, to minimize synchronicity assumptions
156 from twisted.internet import protocol, reactor, defer
157 from twisted.protocols import basic
158 class FingerProtocol(basic.LineReceiver):
159     def lineReceived(self, user):
160         self.factory.getUser(user
161         ).addErrback(lambda _: "Internal error in server"
162         ).addCallback(lambda m:
163             (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
164 class FingerFactory(protocol.ServerFactory):
165     protocol = FingerProtocol
166     def __init__(self, **kwargs): self.users = kwargs
167     def getUser(self, user):
168         return defer.succeed(self.users.get(user, "No such user"))
169 reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
170 reactor.run()
171 </pre>
172
173 <p>But, here we tweak it just for the hell of it. Yes, while the
174 previous version worked, it did assume the result of getUser is
175 always immediately available. But what if instead of an in memory
176 database, we would have to fetch result from a remote Oracle?
177 Or from the web? Or, or...</p>
178
179 <h2>Run 'finger' Locally</h2>
180
181 <pre>
182 # Read username, output from factory interfacing to OS, drop connections
183 from twisted.internet import protocol, reactor, defer, utils
184 from twisted.protocols import basic
185 class FingerProtocol(basic.LineReceiver):
186     def lineReceived(self, user):
187         self.factory.getUser(user
188         ).addErrback(lambda _: "Internal error in server"
189         ).addCallback(lambda m:
190             (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
191 class FingerFactory(protocol.ServerFactory):
192     protocol = FingerProtocol
193     def getUser(self, user):
194         return utils.getProcessOutput("finger", [user])
195 reactor.listenTCP(1079, FingerFactory())
196 reactor.run()
197 </pre>
198
199 <p>...from running a local command? Yes, this version (safely!) runs
200 finger locally with whatever arguments it is given, and returns the
201 standard output. This will do exactly what the standard version
202 of the finger server does -- without the need for any remote buffer
203 overflows, as the networking is done safely.</p>
204
205 <h2>Read Status from the Web</h2>
206
207 <pre>
208 # Read username, output from factory interfacing to web, drop connections
209 from twisted.internet import protocol, reactor, defer, utils
210 from twisted.protocols import basic
211 from twisted.web import client
212 class FingerProtocol(basic.LineReceiver):
213     def lineReceived(self, user):
214         self.factory.getUser(user
215         ).addErrback(lambda _: "Internal error in server"
216         ).addCallback(lambda m:
217             (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
218 class FingerFactory(protocol.ServerFactory):
219     protocol = FingerProtocol
220     def __init__(self, prefix): self.prefix=prefix
221     def getUser(self, user):
222         return client.getPage(self.prefix+user)
223 reactor.listenTCP(1079, FingerFactory(prefix='http://livejournal.com/~'))
224 reactor.run()
225 </pre>
226
227 <p>The web. That invention which has infiltrated homes around the
228 world finally gets through to our invention. Here we use the built-in
229 Twisted web client, which also returns a deferred. Finally, we manage
230 to have examples of three different database backends, which do
231 not change the protocol class. In fact, we will not have to change
232 the protocol again until the end of this talk: we have achieved,
233 here, one truly usable class.</p>
234
235
236 <h2>Use Application</h2>
237
238 <pre>
239 # Read username, output from non-empty factory, drop connections
240 # Use deferreds, to minimize synchronicity assumptions
241 # Write application. Save in 'finger.tpy'
242 from twisted.internet import protocol, reactor, defer, app
243 from twisted.protocols import basic
244 class FingerProtocol(basic.LineReceiver):
245     def lineReceived(self, user):
246         self.factory.getUser(user
247         ).addErrback(lambda _: "Internal error in server"
248         ).addCallback(lambda m:
249             (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
250 class FingerFactory(protocol.ServerFactory):
251     protocol = FingerProtocol
252     def __init__(self, **kwargs): self.users = kwargs
253     def getUser(self, user):
254         return defer.succeed(self.users.get(user, "No such user"))
255 application = app.Application('finger', uid=1, gid=1)
256 application.listenTCP(79, FingerFactory(moshez='Happy and well'))
257 </pre>
258
259 <p>Up until now, we faked. We kept using port 1079, because really,
260 who wants to run a finger server with root privileges? Well, the
261 common solution is "privilege shedding": after binding to the network,
262 become a different, less privileged user. We could have done it ourselves,
263 but Twisted has a builtin way to do it. Create a snippet as above,
264 defining an application object. That object will have uid and gid
265 attributes. When running it (later we will see how) it will bind
266 to ports, shed privileges and then run.</p>
267
268 <h2>twistd</h2>
269
270 <pre>
271 root% twistd -ny finger.tpy # just like before
272 root% twistd -y finger.tpy # daemonize, keep pid in twistd.pid
273 root% twistd -y finger.tpy --pidfile=finger.pid
274 root% twistd -y finger.tpy --rundir=/
275 root% twistd -y finger.tpy --chroot=/var
276 root% twistd -y finger.tpy -l /var/log/finger.log
277 root% twistd -y finger.tpy --syslog # just log to syslog
278 root% twistd -y finger.tpy --syslog --prefix=twistedfinger # use given prefix
279 </pre>
280
281 <p>This is how to run "Twisted Applications" -- files which define an
282 'application'. twistd (TWISTed Daemonizer) does everything a daemon
283 can be expected to -- shuts down stdin/stdout/stderr, disconnects
284 from the terminal and can even change runtime directory, or even
285 the root filesystems. In short, it does everything so the Twisted
286 application developer can concentrate on writing his networking code.</p>
287
288 <h2>Setting Message By Local Users</h2>
289
290 <pre>
291 # But let's try and fix setting away messages, shall we?
292 from twisted.internet import protocol, reactor, defer, app
293 from twisted.protocols import basic
294 class FingerProtocol(basic.LineReceiver):
295     def lineReceived(self, user):
296         self.factory.getUser(user
297         ).addErrback(lambda _: "Internal error in server"
298         ).addCallback(lambda m:
299             (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
300 class FingerFactory(protocol.ServerFactory):
301     protocol = FingerProtocol
302     def __init__(self, **kwargs): self.users = kwargs
303     def getUser(self, user):
304         return defer.succeed(self.users.get(user, "No such user"))
305 class FingerSetterProtocol(basic.LineReceiver):
306       def connectionMade(self): self.lines = []
307       def lineReceived(self, line): self.lines.append(line)
308       def connectionLost(self): self.factory.setUser(*self.lines)
309 class FingerSetterFactory(protocol.ServerFactory):
310       def __init__(self, ff): self.setUser = self.ff.users.__setitem__
311 ff = FingerFactory(moshez='Happy and well')
312 fsf = FingerSetterFactory(ff)
313 application = app.Application('finger', uid=1, gid=1)
314 application.listenTCP(79, ff)
315 application.listenTCP(1079, fsf, interface='127.0.0.1')
316 </pre>
317
318 <p>Now that port 1079 is free, maybe we can run on it a different
319 server, one which will let people set their messages. It does
320 no access control, so anyone who can login to the machine can
321 set any message. We assume this is the desired behaviour in
322 our case. Testing it can be done by simply:
323 </p>
324
325 <pre>
326 % nc localhost 1079
327 moshez
328 Giving a talk now, sorry!
329 ^D
330 </pre>
331
332 <h2>Use Services to Make Dependencies Sane</h2>
333
334 <pre>
335 # Fix asymmetry
336 from twisted.internet import protocol, reactor, defer, app
337 from twisted.protocols import basic
338 class FingerProtocol(basic.LineReceiver):
339     def lineReceived(self, user):
340         self.factory.getUser(user
341         ).addErrback(lambda _: "Internal error in server"
342         ).addCallback(lambda m:
343             (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
344 class FingerSetterProtocol(basic.LineReceiver):
345       def connectionMade(self): self.lines = []
346       def lineReceived(self, line): self.lines.append(line)
347       def connectionLost(self): self.factory.setUser(*self.lines)
348 class FingerService(app.ApplicationService):
349       def __init__(self, *args, **kwargs):
350           app.ApplicationService.__init__(self, *args)
351           self.users = kwargs
352       def getUser(self, user):
353           return defer.succeed(self.users.get(u, "No such user"))
354       def getFingerFactory(self):
355           f = protocol.ServerFactory()
356           f.protocol, f.getUser = FingerProtocol, self.getUser
357           return f
358       def getFingerSetterFactory(self):
359           f = protocol.ServerFactory()
360           f.protocol, f.setUser = FingerSetterProtocol, self.users.__setitem__
361           return f
362 application = app.Application('finger', uid=1, gid=1)
363 f = FingerService(application, 'finger', moshez='Happy and well')
364 application.listenTCP(79, f.getFingerFactory())
365 application.listenTCP(1079, f.getFingerSetterFactory(), interface='127.0.0.1')
366 </pre>
367
368 <p>The previous version had the setter poke at the innards of the
369 finger factory. It's usually not a good idea: this version makes
370 both factories symmetric by making them both look at a single
371 object. Services are useful for when an object is needed which is
372 not related to a specific network server. Here, we moved all responsibility
373 for manufacturing factories into the service. Note that we stopped
374 subclassing: the service simply puts useful methods and attributes
375 inside the factories. We are getting better at protocol design:
376 none of our protocol classes had to be changed, and neither will
377 have to change until the end of the talk.</p>
378
379 <h2>Read Status File</h2>
380
381 <pre>
382 # Read from file
383 from twisted.internet import protocol, reactor, defer, app
384 from twisted.protocols import basic
385 class FingerProtocol(basic.LineReceiver):
386     def lineReceived(self, user):
387         self.factory.getUser(user
388         ).addErrback(lambda _: "Internal error in server"
389         ).addCallback(lambda m:
390             (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
391 class FingerSetterProtocol(basic.LineReceiver):
392       def connectionMade(self): self.lines = []
393       def lineReceived(self, line): self.lines.append(line)
394       def connectionLost(self): self.factory.setUser(*self.lines)
395 class FingerService(app.ApplicationService):
396       def __init__(self, file, *args, **kwargs):
397           app.ApplicationService.__init__(self, *args, **kwargs)
398           self.file = file
399       def startService(self):
400           app.ApplicationService.startService(self)
401           self._read()
402       def _read(self):
403           self.users = {}
404           for line in file(self.file):
405               user, status = line.split(':', 1)
406               self.users[user] = status
407           self.call = reactor.callLater(30, self._read)
408       def stopService(self):
409           app.ApplicationService.stopService(self)
410           self.call.cancel()
411       def getUser(self, user):
412           return defer.succeed(self.users.get(u, "No such user"))
413       def getFingerFactory(self):
414           f = protocol.ServerFactory()
415           f.protocol, f.getUser = FingerProtocol, self.getUser
416           return f
417 application = app.Application('finger', uid=1, gid=1)
418 f = FingerService('/etc/users', application, 'finger')
419 application.listenTCP(79, f.getFingerFactory())
420 </pre>
421
422 <p>This version shows how, instead of just letting users set their
423 messages, we can read those from a centrally managed file. We cache
424 results, and every 30 seconds we refresh it. Services are useful
425 for such scheduled tasks.</p>
426
427 <h2>Announce on Web, Too</h2>
428
429 <pre>
430 # Read from file, announce on the web!
431 from twisted.internet import protocol, reactor, defer, app
432 from twisted.protocols import basic
433 from twisted.web import resource, server, static
434 import cgi
435 class FingerProtocol(basic.LineReceiver):
436     def lineReceived(self, user):
437         self.factory.getUser(user
438         ).addErrback(lambda _: "Internal error in server"
439         ).addCallback(lambda m:
440             (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
441 class FingerSetterProtocol(basic.LineReceiver):
442       def connectionMade(self): self.lines = []
443       def lineReceived(self, line): self.lines.append(line)
444       def connectionLost(self): self.factory.setUser(*self.lines)
445 class FingerService(app.ApplicationService):
446       def __init__(self, file, *args, **kwargs):
447           app.ApplicationService.__init__(self, *args, **kwargs)
448           self.file = file
449       def startService(self):
450           app.ApplicationService.startService(self)
451           self._read()
452       def _read(self):
453           self.users = {}
454           for line in file(self.file):
455               user, status = line.split(':', 1)
456               self.users[user] = status
457           self.call = reactor.callLater(30, self._read)
458       def stopService(self):
459           app.ApplicationService.stopService(self)
460           self.call.cancel()
461       def getUser(self, user):
462           return defer.succeed(self.users.get(u, "No such user"))
463       def getFingerFactory(self):
464           f = protocol.ServerFactory()
465           f.protocol, f.getUser = FingerProtocol, self.getUser
466           return f
467       def getResource(self):
468           r = resource.Resource()
469           r.getChild = (lambda path, request:
470            static.Data('text/html',
471             '&lt;h1>%s&lt;/h1>&lt;p>%s&lt;/p>' %
472               tuple(map(cgi.escape,
473                         [path,self.users.get(path, "No such user")]))))
474 application = app.Application('finger', uid=1, gid=1)
475 f = FingerService('/etc/users', application, 'finger')
476 application.listenTCP(79, f.getFingerFactory())
477 application.listenTCP(80, server.Site(f.getResource()))
478 </pre>
479
480 <p>The same kind of service can also produce things useful for
481 other protocols. For example, in twisted.web, the factory
482 itself (the site) is almost never subclassed -- instead,
483 it is given a resource, which represents the tree of resources
484 available via URLs. That hierarchy is navigated by site,
485 and overriding it dynamically is possible with getChild.</p>
486
487 <h2>Announce on IRC, Too</h2>
488
489 <pre>
490 # Read from file, announce on the web, irc
491 from twisted.internet import protocol, reactor, defer, app
492 from twisted.protocols import basic, irc
493 from twisted.web import resource, server, static
494 import cgi
495 class FingerProtocol(basic.LineReceiver):
496     def lineReceived(self, user):
497         self.factory.getUser(user
498         ).addErrback(lambda _: "Internal error in server"
499         ).addCallback(lambda m:
500             (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
501 class FingerSetterProtocol(basic.LineReceiver):
502       def connectionMade(self): self.lines = []
503       def lineReceived(self, line): self.lines.append(line)
504       def connectionLost(self): self.factory.setUser(*self.lines)
505 class IRCReplyBot(irc.IRCClient):
506     def connectionMade(self):
507         self.nickname = self.factory.nickname
508         irc.IRCClient.connectionMade(self)
509     def privmsg(self, user, channel, msg):
510         if user.lower() == channel.lower():
511             self.factory.getUser(msg
512             ).addErrback(lambda _: "Internal error in server"
513             ).addCallback(lambda m: self.msg(user, m))
514 class FingerService(app.ApplicationService):
515       def __init__(self, file, *args, **kwargs):
516           app.ApplicationService.__init__(self, *args, **kwargs)
517           self.file = file
518       def startService(self):
519           app.ApplicationService.startService(self)
520           self._read()
521       def _read(self):
522           self.users = {}
523           for line in file(self.file):
524               user, status = line.split(':', 1)
525               self.users[user] = status
526           self.call = reactor.callLater(30, self._read)
527       def stopService(self):
528           app.ApplicationService.stopService(self)
529           self.call.cancel()
530       def getUser(self, user):
531           return defer.succeed(self.users.get(u, "No such user"))
532       def getFingerFactory(self):
533           f = protocol.ServerFactory()
534           f.protocol, f.getUser = FingerProtocol, self.getUser
535           return f
536       def getResource(self):
537           r = resource.Resource()
538           r.getChild = (lambda path, request:
539            static.Data('text/html',
540             '&lt;h1>%s&lt;/h1>&lt;p>%s&lt;/p>' %
541               tuple(map(cgi.escape,
542                         [path,self.users.get(path, "No such user")]))))
543       def getIRCBot(self, nickname):
544           f = protocol.ReconnectingClientFactory()
545           f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser
546           return f
547 application = app.Application('finger', uid=1, gid=1)
548 f = FingerService('/etc/users', application, 'finger')
549 application.listenTCP(79, f.getFingerFactory())
550 application.listenTCP(80, server.Site(f.getResource()))
551 application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot'))
552 </pre>
553
554 <p>This is the first time there is client code. IRC clients often
555 act a lot like servers: responding to events form the network.
556 The reconnecting client factory will make sure that severed links
557 will get re-established, with intelligent tweaked exponential
558 backoff algorithms. The irc client itself is simple: the only
559 real hack is getting the nickname from the factory in connectionMade.</p>
560
561
562
563 <h2>Add XML-RPC Support</h2>
564
565 <pre>
566 # Read from file, announce on the web, irc, xml-rpc
567 from twisted.internet import protocol, reactor, defer, app
568 from twisted.protocols import basic, irc
569 from twisted.web import resource, server, static, xmlrpc
570 import cgi
571 class FingerProtocol(basic.LineReceiver):
572     def lineReceived(self, user):
573         self.factory.getUser(user
574         ).addErrback(lambda _: "Internal error in server"
575         ).addCallback(lambda m:
576             (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
577 class FingerSetterProtocol(basic.LineReceiver):
578       def connectionMade(self): self.lines = []
579       def lineReceived(self, line): self.lines.append(line)
580       def connectionLost(self): self.factory.setUser(*self.lines)
581 class IRCReplyBot(irc.IRCClient):
582     def connectionMade(self):
583         self.nickname = self.factory.nickname
584         irc.IRCClient.connectionMade(self)
585     def privmsg(self, user, channel, msg):
586         if user.lower() == channel.lower():
587             self.factory.getUser(msg
588         ).addErrback(lambda _: "Internal error in server"
589         ).addCallback(lambda m: self.msg(user, m))
590 class FingerService(app.ApplicationService):
591       def __init__(self, file, *args, **kwargs):
592           app.ApplicationService.__init__(self, *args, **kwargs)
593           self.file = file
594       def startService(self):
595           app.ApplicationService.startService(self)
596           self._read()
597       def _read(self):
598           self.users = {}
599           for line in file(self.file):
600               user, status = line.split(':', 1)
601               self.users[user] = status
602           self.call = reactor.callLater(30, self._read)
603       def stopService(self):
604           app.ApplicationService.stopService(self)
605           self.call.cancel()
606       def getUser(self, user):
607           return defer.succeed(self.users.get(u, "No such user"))
608       def getFingerFactory(self):
609           f = protocol.ServerFactory()
610           f.protocol, f.getUser = FingerProtocol, self.getUser
611           return f
612       def getResource(self):
613           r = resource.Resource()
614           r.getChild = (lambda path, request:
615            static.Data('text/html',
616             '&lt;h1>%s&lt;/h1>&lt;p>%s&lt;/p>' %
617               tuple(map(cgi.escape,
618                         [path,self.users.get(path, "No such user")]))))
619           x = xmlrpc.XMLRPRC()
620           x.xmlrpc_getUser = self.getUser
621           r.putChild('RPC2.0', x)
622           return r
623       def getIRCBot(self, nickname):
624           f = protocol.ReconnectingClientFactory()
625           f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser
626           return f
627 application = app.Application('finger', uid=1, gid=1)
628 f = FingerService('/etc/users', application, 'finger')
629 application.listenTCP(79, f.getFingerFactory())
630 application.listenTCP(80, server.Site(f.getResource()))
631 application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot'))
632 </pre>
633
634 <p>In Twisted, XML-RPC support is handled just as though it was
635 another resource. That resource will still support GET calls normally
636 through render(), but that is usually left unimplemented. Note
637 that it is possible to return deferreds from XML-RPC methods.
638 The client, of course, will not get the answer until the deferred
639 is triggered.</p>
640
641
642 <h2>Write Readable Code</h2>
643
644 <pre>
645 # Do everything properly
646 from twisted.internet import protocol, reactor, defer, app
647 from twisted.protocols import basic, irc
648 from twisted.web import resource, server, static, xmlrpc
649 import cgi
650
651 def catchError(err):
652     return "Internal error in server"
653
654 class FingerProtocol(basic.LineReceiver):
655
656     def lineReceived(self, user):
657         d = self.factory.getUser(user)
658         d.addErrback(catchError)
659         def writeValue(value):
660             self.transport.write(value)
661             self.transport.loseConnection()
662         d.addCallback(writeValue)
663
664
665 class FingerSetterProtocol(basic.LineReceiver):
666
667       def connectionMade(self):
668           self.lines = []
669
670       def lineReceived(self, line):
671           self.lines.append(line)
672
673       def connectionLost(self):
674           if len(self.lines) == 2:
675               self.factory.setUser(*self.lines)
676
677
678 class IRCReplyBot(irc.IRCClient):
679
680     def connectionMade(self):
681         self.nickname = self.factory.nickname
682         irc.IRCClient.connectionMade(self)
683
684     def privmsg(self, user, channel, msg):
685         if user.lower() == channel.lower():
686             d = self.factory.getUser(msg)
687             d.addErrback(catchError)
688             d.addCallback(lambda m: "Status of %s: %s" % (user, m))
689             d.addCallback(lambda m: self.msg(user, m))
690
691
692 class UserStatusTree(resource.Resource):
693
694     def __init__(self, service):
695         resource.Resource.__init__(self):
696         self.service = service
697
698     def render(self, request):
699         d = self.service.getUsers()
700         def formatUsers(users):
701             l = ["&lt;li>&lt;a href="%s">%s&lt;/a>&lt;/li> % (user, user)
702                 for user in users]
703             return '&lt;ul>'+''.join(l)+'&lt;/ul>'
704         d.addCallback(formatUsers)
705         d.addCallback(request.write)
706         d.addCallback(lambda _: request.finish())
707         return server.NOT_DONE_YET
708
709     def getChild(self, path, request):
710         return UserStatus(path, self.service)
711
712
713 class UserStatus(resource.Resource):
714
715     def __init__(self, user, service):
716         resource.Resource.__init__(self):
717         self.user = user
718         self.service = service
719
720     def render(self, request):
721         d = self.service.getUser(self.user)
722         d.addCallback(cgi.escape)
723         d.addCallback(lambda m:
724                      '&lt;h1>%s&lt;/h1>'%self.user+'&lt;p>%s&lt;/p>'%m)
725         d.addCallback(request.write)
726         d.addCallback(lambda _: request.finish())
727         return server.NOT_DONE_YET
728
729
730 class UserStatusXR(xmlrpc.XMLPRC):
731
732     def __init__(self, service):
733         xmlrpc.XMLRPC.__init__(self)
734         self.service = service
735
736     def xmlrpc_getUser(self, user):
737         return self.service.getUser(user)
738
739
740 class FingerService(app.ApplicationService):
741
742       def __init__(self, file, *args, **kwargs):
743           app.ApplicationService.__init__(self, *args, **kwargs)
744           self.file = file
745
746       def startService(self):
747           app.ApplicationService.startService(self)
748           self._read()
749
750       def _read(self):
751           self.users = {}
752           for line in file(self.file):
753               user, status = line.split(':', 1)
754               self.users[user] = status
755           self.call = reactor.callLater(30, self._read)
756
757       def stopService(self):
758           app.ApplicationService.stopService(self)
759           self.call.cancel()
760
761       def getUser(self, user):
762           return defer.succeed(self.users.get(u, "No such user"))
763
764       def getUsers(self):
765           return defer.succeed(self.users.keys())
766
767       def getFingerFactory(self):
768           f = protocol.ServerFactory()
769           f.protocol = FingerProtocol
770           f.getUser = self.getUser
771           return f
772
773       def getResource(self):
774           r = UserStatusTree(self)
775           x = UserStatusXR(self)
776           r.putChild('RPC2.0', x)
777           return r
778
779       def getIRCBot(self, nickname):
780           f = protocol.ReconnectingClientFactory()
781           f.protocol = IRCReplyBot
782           f.nickname = nickname
783           f.getUser = self.getUser
784           return f
785
786 application = app.Application('finger', uid=1, gid=1)
787 f = FingerService('/etc/users', application, 'finger')
788 application.listenTCP(79, f.getFingerFactory())
789 application.listenTCP(80, server.Site(f.getResource()))
790 application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot'))
791 </pre>
792
793 <p>The last version of the application had a lot of hacks. We avoided
794 subclassing, did not support things like user listings in the web
795 support, and removed all blank lines -- all in the interest of code
796 which is shorter. Here we take a step back, subclass what is more
797 naturally a subclass, make things which should take multiple lines
798 take them, etc. This shows a much better style of developing Twisted
799 applications, though the hacks in the previous stages are sometimes
800 used in throw-away prototypes.</p>
801
802 <h2>Write Maintainable Code</h2>
803
804 <pre>
805 # Do everything properly, and componentize
806 from twisted.internet import protocol, reactor, defer, app
807 from twisted.protocols import basic, irc
808 from twisted.python import components
809 from twisted.web import resource, server, static, xmlrpc
810 import cgi
811
812 class IFingerService(components.Interface):
813
814     def getUser(self, user):
815         '''Return a deferred returning a string'''
816
817     def getUsers(self):
818         '''Return a deferred returning a list of strings'''
819
820 class IFingerSettingService(components.Interface):
821
822     def setUser(self, user, status):
823         '''Set the user's status to something'''
824     
825 def catchError(err):
826     return "Internal error in server"
827
828
829 class FingerProtocol(basic.LineReceiver):
830
831     def lineReceived(self, user):
832         d = self.factory.getUser(user)
833         d.addErrback(catchError)
834         def writeValue(value):
835             self.transport.write(value)
836             self.transport.loseConnection()
837         d.addCallback(writeValue)
838
839
840 class IFingerFactory(components.Interface):
841
842     def getUser(self, user):
843         """Return a deferred returning a string""""
844
845     def buildProtocol(self, addr):
846         """Return a protocol returning a string""""
847
848
849 class FingerFactoryFromService(protocol.ServerFactory):
850
851     __implements__ = IFingerFactory,
852
853     protocol = FingerProtocol
854
855     def __init__(self, service):
856         self.service = service
857
858     def getUser(self, user):
859         return self.service.getUser(user)
860
861 components.registerAdapter(FingerFactoryFromService, IFingerService)
862
863
864 class FingerSetterProtocol(basic.LineReceiver):
865
866       def connectionMade(self):
867           self.lines = []
868
869       def lineReceived(self, line):
870           self.lines.append(line)
871
872       def connectionLost(self):
873           if len(self.lines) == 2:
874               self.factory.setUser(*self.lines)
875
876
877 class IFingerSetterFactory(components.Interface):
878
879     def setUser(self, user, status):
880         """Return a deferred returning a string"""
881
882     def buildProtocol(self, addr):
883         """Return a protocol returning a string"""
884
885
886 class FingerSetterFactoryFromService(protocol.ServerFactory):
887
888     __implements__ = IFingerSetterFactory,
889
890     protocol = FingerSetterProtocol
891
892     def __init__(self, service):
893         self.service = service
894
895     def setUser(self, user, status):
896         self.service.setUser(user, status)
897
898
899 components.registerAdapter(FingerSetterFactoryFromService,
900                            IFingerSettingService)
901     
902 class IRCReplyBot(irc.IRCClient):
903
904     def connectionMade(self):
905         self.nickname = self.factory.nickname
906         irc.IRCClient.connectionMade(self)
907
908     def privmsg(self, user, channel, msg):
909         if user.lower() == channel.lower():
910             d = self.factory.getUser(msg)
911             d.addErrback(catchError)
912             d.addCallback(lambda m: "Status of %s: %s" % (user, m))
913             d.addCallback(lambda m: self.msg(user, m))
914
915
916 class IIRCClientFactory(components.Interface):
917
918     '''
919     @ivar nickname
920     '''
921
922     def getUser(self, user):
923         """Return a deferred returning a string"""
924
925     def buildProtocol(self, addr):
926         """Return a protocol"""
927
928
929 class IRCClientFactoryFromService(protocol.ClientFactory):
930
931    __implements__ = IIRCClientFactory,
932
933    protocol = IRCReplyBot
934    nickname = None
935
936    def __init__(self, service):
937        self.service = service
938
939     def getUser(self, user):
940         return self.service.getUser()
941
942 components.registerAdapter(IRCClientFactoryFromService, IFingerService)
943
944 class UserStatusTree(resource.Resource):
945
946     def __init__(self, service):
947         resource.Resource.__init__(self):
948         self.putChild('RPC2.0', UserStatusXR(self.service))
949         self.service = service
950
951     def render(self, request):
952         d = self.service.getUsers()
953         def formatUsers(users):
954             l = ["&lt;li>&lt;a href="%s">%s&lt;/a>&lt;/li> % (user, user)
955                 for user in users]
956             return '&lt;ul>'+''.join(l)+'&lt;/ul>'
957         d.addCallback(formatUsers)
958         d.addCallback(request.write)
959         d.addCallback(lambda _: request.finish())
960         return server.NOT_DONE_YET
961
962     def getChild(self, path, request):
963         return UserStatus(path, self.service)
964
965 components.registerAdapter(UserStatusTree, IFingerService)
966
967 class UserStatus(resource.Resource):
968
969     def __init__(self, user, service):
970         resource.Resource.__init__(self):
971         self.user = user
972         self.service = service
973
974     def render(self, request):
975         d = self.service.getUser(self.user)
976         d.addCallback(cgi.escape)
977         d.addCallback(lambda m:
978                       '&lt;h1>%s&lt;/h1>'%self.user+'&lt;p>%s&lt;/p>'%m)
979         d.addCallback(request.write)
980         d.addCallback(lambda _: request.finish())
981         return server.NOT_DONE_YET
982
983
984 class UserStatusXR(xmlrpc.XMLPRC):
985
986     def __init__(self, service):
987         xmlrpc.XMLRPC.__init__(self)
988         self.service = service
989
990     def xmlrpc_getUser(self, user):
991         return self.service.getUser(user)
992
993
994 class FingerService(app.ApplicationService):
995
996     __implements__ = IFingerService,
997
998     def __init__(self, file, *args, **kwargs):
999         app.ApplicationService.__init__(self, *args, **kwargs)
1000         self.file = file
1001
1002     def startService(self):
1003         app.ApplicationService.startService(self)
1004         self._read()
1005
1006     def _read(self):
1007         self.users = {}
1008         for line in file(self.file):
1009             user, status = line.split(':', 1)
1010             self.users[user] = status
1011         self.call = reactor.callLater(30, self._read)
1012
1013     def stopService(self):
1014         app.ApplicationService.stopService(self)
1015         self.call.cancel()
1016
1017     def getUser(self, user):
1018         return defer.succeed(self.users.get(u, "No such user"))
1019
1020     def getUsers(self):
1021         return defer.succeed(self.users.keys())
1022
1023
1024 application = app.Application('finger', uid=1, gid=1)
1025 f = FingerService('/etc/users', application, 'finger')
1026 application.listenTCP(79, IFingerFactory(f))
1027 application.listenTCP(80, server.Site(resource.IResource(f)))
1028 i = IIRCClientFactory(f)
1029 i.nickname = 'fingerbot'
1030 application.connectTCP('irc.freenode.org', 6667, i)
1031 </pre>
1032
1033 <p>In the last version, the service class was three times longer than
1034 any other class, and was hard to understand. This was because it turned
1035 out to have multiple responsibilities. It had to know how to access
1036 user information, by scheduling a reread of the file ever half minute,
1037 but also how to display itself in a myriad of protocols. Here, we
1038 used the component-based architecture that Twisted provides to achieve
1039 a separation of concerns. All the service is responsible for, now,
1040 is supporting getUser/getUsers. It declares its support via the
1041 __implements__ keyword. Then, adapters are used to make this service
1042 look like an appropriate class for various things: for supplying
1043 a finger factory to listenTCP, for supplying a resource to site's
1044 constructor, and to provide an IRC client factory for connectTCP.
1045 All the adapters use are the methods in FingerService they are
1046 declared to use: getUser/getUsers. We could, of course,
1047 skipped the interfaces and let the configuration code use
1048 things like FingerFactoryFromService(f) directly. However, using
1049 interfaces provides the same flexibility inheritance gives: future
1050 subclasses can override the adapters.</p>
1051
1052
1053
1054 <h2>Advantages of Latest Version</h2>
1055
1056 <ul>
1057 <li>Readable -- each class is short</li>
1058 <li>Maintainable -- each class knows only about interfaces</li>
1059 <li>Dependencies between code parts are minimized</li>
1060 <li>Example: writing a new IFingerService is easy</li>
1061 </ul>
1062
1063 <pre>
1064 class MemoryFingerService(app.ApplicationService):
1065       __implements__ = IFingerService, IFingerSetterService
1066
1067       def __init__(self, *args, **kwargs):
1068           app.ApplicationService.__init__(self, *args)
1069           self.users = kwargs
1070
1071       def getUser(self, user):
1072           return defer.succeed(self.users.get(u, "No such user"))
1073
1074       def getUsers(self):
1075           return defer.succeed(self.users.keys())
1076
1077       def setUser(self, user, status):
1078           self.users[user] = status
1079
1080 application = app.Application('finger', uid=1, gid=1)
1081 # New constructor call
1082 f = MemoryFingerService(application, 'finger', moshez='Happy and well')
1083 application.listenTCP(79, IFingerFactory(f))
1084 application.listenTCP(80, server.Site(resource.IResource(f)))
1085 i = IIRCClientFactory(f)
1086 i.nickname = 'fingerbot'
1087 application.connectTCP('irc.freenode.org', 6667, i)
1088 # New: run setter too
1089 application.listenTCP(1079, IFingerSetterFactory(f), interface='127.0.0.1')
1090 </pre>
1091
1092 <p>Here we show just how convenient it is to implement new backends
1093 when we move to a component based architecture. Note that here
1094 we also use an interface we previously wrote, FingerSetterFactory,
1095 by supporting one single method. We manage to preserve the service's
1096 ignorance of the network.</p>
1097
1098 <h2>Another Backend</h2>
1099
1100 <pre>
1101 class LocalFingerService(app.ApplicationService):
1102       __implements__ = IFingerService
1103
1104       def getUser(self, user):
1105           return utils.getProcessOutput("finger", [user])
1106
1107       def getUsers(self):
1108           return defer.succeed([])
1109
1110 application = app.Application('finger', uid=1, gid=1)
1111 f = LocalFingerService(application, 'finger')
1112 application.listenTCP(79, IFingerFactory(f))
1113 application.listenTCP(80, server.Site(resource.IResource(f)))
1114 i = IIRCClientFactory(f)
1115 i.nickname = 'fingerbot'
1116 application.connectTCP('irc.freenode.org', 6667, i)
1117 </pre>
1118
1119 <p>We have already wrote this, but now we get more for less work:
1120 the network code is completely separate from the backend.</p>
1121
1122 <h2>Yet Another Backend: Doing the Standard Thing</h2>
1123
1124 <pre>
1125 import pwd
1126
1127 class LocalFingerService(app.ApplicationService):
1128       __implements__ = IFingerService
1129
1130       def getUser(self, user):
1131           try:
1132               entry = pwd.getpwnam(user)
1133           except KeyError:
1134               return "No such user"
1135           try:
1136               f=file(os.path.join(entry[5],'.plan'))
1137           except (IOError, OSError):
1138               return "No such user"
1139           data = f.read()
1140           f.close()
1141           return data
1142
1143       def getUsers(self):
1144           return defer.succeed([])
1145
1146 application = app.Application('finger', uid=1, gid=1)
1147 f = LocalFingerService(application, 'finger')
1148 application.listenTCP(79, IFingerFactory(f))
1149 application.listenTCP(80, server.Site(resource.IResource(f)))
1150 i = IIRCClientFactory(f)
1151 i.nickname = 'fingerbot'
1152 application.connectTCP('irc.freenode.org', 6667, i)
1153 </pre>
1154
1155 <p>Not much to say about that, except to indicate that by now we
1156 can be churning out backends like crazy. Feel like doing a backend
1157 for advogato, for example? Dig out the XML-RPC client support Twisted
1158 has, and get to work!</p>
1159
1160
1161 <h2>Aspect Oriented Programming</h2>
1162
1163 <ul>
1164 <li>This is an example...</li>
1165 <li>...with something actually useful...</li>
1166 <li>...not logging and timing.</li>
1167 <li>Write less code, have less dependencies!</li>
1168 </ul>
1169
1170 <h2>Use Woven</h2>
1171
1172 <pre>
1173 # Do everything properly, and componentize
1174 from twisted.internet import protocol, reactor, defer, app
1175 from twisted.protocols import basic, irc
1176 from twisted.python import components
1177 from twisted.web import resource, server, static, xmlrpc, microdom
1178 from twisted.web.woven import page, widget
1179 import cgi
1180
1181 class IFingerService(components.Interface):
1182
1183     def getUser(self, user):
1184         '''Return a deferred returning a string'''
1185
1186     def getUsers(self):
1187         '''Return a deferred returning a list of strings'''
1188
1189 class IFingerSettingService(components.Interface):
1190
1191     def setUser(self, user, status):
1192         '''Set the user's status to something'''
1193     
1194 def catchError(err):
1195     return "Internal error in server"
1196
1197
1198 class FingerProtocol(basic.LineReceiver):
1199
1200     def lineReceived(self, user):
1201         d = self.factory.getUser(user)
1202         d.addErrback(catchError)
1203         def writeValue(value):
1204             self.transport.write(value)
1205             self.transport.loseConnection()
1206         d.addCallback(writeValue)
1207
1208
1209 class IFingerFactory(components.Interface):
1210
1211     def getUser(self, user):
1212         """Return a deferred returning a string""""
1213
1214     def buildProtocol(self, addr):
1215         """Return a protocol returning a string""""
1216
1217
1218 class FingerFactoryFromService(protocol.ServerFactory):
1219
1220     __implements__ = IFingerFactory,
1221
1222     protocol = FingerProtocol
1223
1224     def __init__(self, service):
1225         self.service = service
1226
1227     def getUser(self, user):
1228         return self.service.getUser(user)
1229
1230 components.registerAdapter(FingerFactoryFromService, IFingerService)
1231
1232
1233 class FingerSetterProtocol(basic.LineReceiver):
1234
1235       def connectionMade(self):
1236           self.lines = []
1237
1238       def lineReceived(self, line):
1239           self.lines.append(line)
1240
1241       def connectionLost(self):
1242           if len(self.lines) == 2:
1243               self.factory.setUser(*self.lines)
1244
1245
1246 class IFingerSetterFactory(components.Interface):
1247
1248     def setUser(self, user, status):
1249         """Return a deferred returning a string"""
1250
1251     def buildProtocol(self, addr):
1252         """Return a protocol returning a string"""
1253
1254
1255 class FingerSetterFactoryFromService(protocol.ServerFactory):
1256
1257     __implements__ = IFingerSetterFactory,
1258
1259     protocol = FingerSetterProtocol
1260
1261     def __init__(self, service):
1262         self.service = service
1263
1264     def setUser(self, user, status):
1265         self.service.setUser(user, status)
1266
1267
1268 components.registerAdapter(FingerSetterFactoryFromService,
1269                            IFingerSettingService)
1270     
1271 class IRCReplyBot(irc.IRCClient):
1272
1273     def connectionMade(self):
1274         self.nickname = self.factory.nickname
1275         irc.IRCClient.connectionMade(self)
1276
1277     def privmsg(self, user, channel, msg):
1278         if user.lower() == channel.lower():
1279             d = self.factory.getUser(msg)
1280             d.addErrback(catchError)
1281             d.addCallback(lambda m: "Status of %s: %s" % (user, m))
1282             d.addCallback(lambda m: self.msg(user, m))
1283
1284
1285 class IIRCClientFactory(components.Interface):
1286
1287     '''
1288     @ivar nickname
1289     '''
1290
1291     def getUser(self, user):
1292         """Return a deferred returning a string"""
1293
1294     def buildProtocol(self, addr):
1295         """Return a protocol"""
1296
1297
1298 class IRCClientFactoryFromService(protocol.ClientFactory):
1299
1300    __implements__ = IIRCClientFactory,
1301
1302    protocol = IRCReplyBot
1303    nickname = None
1304
1305    def __init__(self, service):
1306        self.service = service
1307
1308     def getUser(self, user):
1309         return self.service.getUser()
1310
1311 components.registerAdapter(IRCClientFactoryFromService, IFingerService)
1312
1313
1314 class UsersModel(model.MethodModel):
1315
1316     def __init__(self, service):
1317         self.service = service
1318
1319     def wmfactory_users(self):
1320         return self.service.getUsers()
1321
1322 components.registerAdapter(UsersModel, IFingerService)
1323
1324 class UserStatusTree(page.Page):
1325
1326     template = """&lt;html>&lt;head>&lt;title>Users&lt;/title>&lt;head>&lt;body>
1327                   &lt;h1>Users&lt;/h1>
1328                   &lt;ul model="users" view="List">
1329                   &lt;li pattern="listItem" />&lt;a view="Link" model="."
1330                   href="dummy">&lt;span model="." view="Text" />&lt;/a>
1331                   &lt;/ul>&lt;/body>&lt;/html>"""
1332
1333     def initialize(self, **kwargs):
1334         self.putChild('RPC2.0', UserStatusXR(self.model.service))
1335
1336     def getDynamicChild(self, path, request):
1337         return UserStatus(user=path, service=self.model.service)
1338
1339 components.registerAdapter(UserStatusTree, IFingerService)
1340
1341
1342 class UserStatus(page.Page):
1343
1344     template='''&lt;html>&lt;head>&lt;title view="Text" model="user"/>&lt;/heaD>
1345     &lt;body>&lt;h1 view="Text" model="user"/>
1346     &lt;p mode="status" view="Text" />
1347     &lt;/body>&lt;/html>'''
1348
1349     def initialize(self, **kwargs):
1350         self.user = kwargs['user']
1351         self.service = kwargs['service']
1352
1353     def wmfactory_user(self):
1354         return self.user
1355
1356     def wmfactory_status(self):
1357         return self.service.getUser(self.user)
1358
1359 class UserStatusXR(xmlrpc.XMLPRC):
1360
1361     def __init__(self, service):
1362         xmlrpc.XMLRPC.__init__(self)
1363         self.service = service
1364
1365     def xmlrpc_getUser(self, user):
1366         return self.service.getUser(user)
1367
1368
1369 class FingerService(app.ApplicationService):
1370
1371     __implements__ = IFingerService,
1372
1373     def __init__(self, file, *args, **kwargs):
1374         app.ApplicationService.__init__(self, *args, **kwargs)
1375         self.file = file
1376
1377     def startService(self):
1378         app.ApplicationService.startService(self)
1379         self._read()
1380
1381     def _read(self):
1382         self.users = {}
1383         for line in file(self.file):
1384             user, status = line.split(':', 1)
1385             self.users[user] = status
1386         self.call = reactor.callLater(30, self._read)
1387
1388     def stopService(self):
1389         app.ApplicationService.stopService(self)
1390         self.call.cancel()
1391
1392     def getUser(self, user):
1393         return defer.succeed(self.users.get(u, "No such user"))
1394
1395     def getUsers(self):
1396         return defer.succeed(self.users.keys())
1397
1398
1399 application = app.Application('finger', uid=1, gid=1)
1400 f = FingerService('/etc/users', application, 'finger')
1401 application.listenTCP(79, IFingerFactory(f))
1402 application.listenTCP(80, server.Site(resource.IResource(f)))
1403 i = IIRCClientFactory(f)
1404 i.nickname = 'fingerbot'
1405 application.connectTCP('irc.freenode.org', 6667, i)
1406 </pre>
1407
1408 <p>Here we convert to using Woven, instead of manually
1409 constructing HTML snippets. Woven is a sophisticated web templating
1410 system. Its main features are to disallow any code inside the HTML,
1411 and transparent integration with deferred results.</p>
1412
1413 <h2>Use Perspective Broker</h2>
1414
1415 <pre>
1416 # Do everything properly, and componentize
1417 from twisted.internet import protocol, reactor, defer, app
1418 from twisted.protocols import basic, irc
1419 from twisted.python import components
1420 from twisted.web import resource, server, static, xmlrpc, microdom
1421 from twisted.web.woven import page, widget
1422 from twisted.spread import pb
1423 import cgi
1424
1425 class IFingerService(components.Interface):
1426
1427     def getUser(self, user):
1428         '''Return a deferred returning a string'''
1429
1430     def getUsers(self):
1431         '''Return a deferred returning a list of strings'''
1432
1433 class IFingerSettingService(components.Interface):
1434
1435     def setUser(self, user, status):
1436         '''Set the user's status to something'''
1437     
1438 def catchError(err):
1439     return "Internal error in server"
1440
1441
1442 class FingerProtocol(basic.LineReceiver):
1443
1444     def lineReceived(self, user):
1445         d = self.factory.getUser(user)
1446         d.addErrback(catchError)
1447         def writeValue(value):
1448             self.transport.write(value)
1449             self.transport.loseConnection()
1450         d.addCallback(writeValue)
1451
1452
1453 class IFingerFactory(components.Interface):
1454
1455     def getUser(self, user):
1456         """Return a deferred returning a string""""
1457
1458     def buildProtocol(self, addr):
1459         """Return a protocol returning a string""""
1460
1461
1462 class FingerFactoryFromService(protocol.ServerFactory):
1463
1464     __implements__ = IFingerFactory,
1465
1466     protocol = FingerProtocol
1467
1468     def __init__(self, service):
1469         self.service = service
1470
1471     def getUser(self, user):
1472         return self.service.getUser(user)
1473
1474 components.registerAdapter(FingerFactoryFromService, IFingerService)
1475
1476
1477 class FingerSetterProtocol(basic.LineReceiver):
1478
1479       def connectionMade(self):
1480           self.lines = []
1481
1482       def lineReceived(self, line):
1483           self.lines.append(line)
1484
1485       def connectionLost(self):
1486           if len(self.lines) == 2:
1487               self.factory.setUser(*self.lines)
1488
1489
1490 class IFingerSetterFactory(components.Interface):
1491
1492     def setUser(self, user, status):
1493         """Return a deferred returning a string"""
1494
1495     def buildProtocol(self, addr):
1496         """Return a protocol returning a string"""
1497
1498
1499 class FingerSetterFactoryFromService(protocol.ServerFactory):
1500
1501     __implements__ = IFingerSetterFactory,
1502
1503     protocol = FingerSetterProtocol
1504
1505     def __init__(self, service):
1506         self.service = service
1507
1508     def setUser(self, user, status):
1509         self.service.setUser(user, status)
1510
1511
1512 components.registerAdapter(FingerSetterFactoryFromService,
1513                            IFingerSettingService)
1514     
1515 class IRCReplyBot(irc.IRCClient):
1516
1517     def connectionMade(self):
1518         self.nickname = self.factory.nickname
1519         irc.IRCClient.connectionMade(self)
1520
1521     def privmsg(self, user, channel, msg):
1522         if user.lower() == channel.lower():
1523             d = self.factory.getUser(msg)
1524             d.addErrback(catchError)
1525             d.addCallback(lambda m: "Status of %s: %s" % (user, m))
1526             d.addCallback(lambda m: self.msg(user, m))
1527
1528
1529 class IIRCClientFactory(components.Interface):
1530
1531     '''
1532     @ivar nickname
1533     '''
1534
1535     def getUser(self, user):
1536         """Return a deferred returning a string"""
1537
1538     def buildProtocol(self, addr):
1539         """Return a protocol"""
1540
1541
1542 class IRCClientFactoryFromService(protocol.ClientFactory):
1543
1544    __implements__ = IIRCClientFactory,
1545
1546    protocol = IRCReplyBot
1547    nickname = None
1548
1549    def __init__(self, service):
1550        self.service = service
1551
1552     def getUser(self, user):
1553         return self.service.getUser()
1554
1555 components.registerAdapter(IRCClientFactoryFromService, IFingerService)
1556
1557
1558 class UsersModel(model.MethodModel):
1559
1560     def __init__(self, service):
1561         self.service = service
1562
1563     def wmfactory_users(self):
1564         return self.service.getUsers()
1565
1566 components.registerAdapter(UsersModel, IFingerService)
1567
1568 class UserStatusTree(page.Page):
1569
1570     template = """&lt;html>&lt;head>&lt;title>Users&lt;/title>&lt;head>&lt;body>
1571                   &lt;h1>Users&lt;/h1>
1572                   &lt;ul model="users" view="List">
1573                   &lt;li pattern="listItem" />&lt;a view="Link" model="."
1574                   href="dummy">&lt;span model="." view="Text" />&lt;/a>
1575                   &lt;/ul>&lt;/body>&lt;/html>"""
1576
1577     def initialize(self, **kwargs):
1578         self.putChild('RPC2.0', UserStatusXR(self.model.service))
1579
1580     def getDynamicChild(self, path, request):
1581         return UserStatus(user=path, service=self.model.service)
1582
1583 components.registerAdapter(UserStatusTree, IFingerService)
1584     
1585
1586 class UserStatus(page.Page):
1587
1588     template='''&lt;html>&lt;head>&lt&lt;title view="Text" model="user"/>&lt;/heaD>
1589     &lt;body>&lt;h1 view="Text" model="user"/>
1590     &lt;p mode="status" view="Text" />
1591     &lt;/body>&lt;/html>'''
1592
1593     def initialize(self, **kwargs):
1594         self.user = kwargs['user']
1595         self.service = kwargs['service']
1596
1597     def wmfactory_user(self):
1598         return self.user
1599
1600     def wmfactory_status(self):
1601         return self.service.getUser(self.user)
1602
1603 class UserStatusXR(xmlrpc.XMLPRC):
1604
1605     def __init__(self, service):
1606         xmlrpc.XMLRPC.__init__(self)
1607         self.service = service
1608
1609     def xmlrpc_getUser(self, user):
1610         return self.service.getUser(user)
1611
1612
1613 class IPerspectiveFinger(components.Interface):
1614
1615     def remote_getUser(self, username):
1616         """return a user's status"""
1617
1618     def remote_getUsers(self):
1619         """return a user's status"""
1620
1621
1622 class PerspectiveFingerFromService(pb.Root):
1623
1624     __implements__ = IPerspectiveFinger,
1625
1626     def __init__(self, service):
1627         self.service = service
1628
1629     def remote_getUser(self, username):
1630         return self.service.getUser(username)
1631
1632     def remote_getUsers(self):
1633         return self.service.getUsers()
1634
1635 components.registerAdapter(PerspectiveFingerFromService, IFingerService)
1636
1637
1638 class FingerService(app.ApplicationService):
1639
1640     __implements__ = IFingerService,
1641
1642     def __init__(self, file, *args, **kwargs):
1643         app.ApplicationService.__init__(self, *args, **kwargs)
1644         self.file = file
1645
1646     def startService(self):
1647         app.ApplicationService.startService(self)
1648         self._read()
1649
1650     def _read(self):
1651         self.users = {}
1652         for line in file(self.file):
1653             user, status = line.split(':', 1)
1654             self.users[user] = status
1655         self.call = reactor.callLater(30, self._read)
1656
1657     def stopService(self):
1658         app.ApplicationService.stopService(self)
1659         self.call.cancel()
1660
1661     def getUser(self, user):
1662         return defer.succeed(self.users.get(u, "No such user"))
1663
1664     def getUsers(self):
1665         return defer.succeed(self.users.keys())
1666
1667
1668 application = app.Application('finger', uid=1, gid=1)
1669 f = FingerService('/etc/users', application, 'finger')
1670 application.listenTCP(79, IFingerFactory(f))
1671 application.listenTCP(80, server.Site(resource.IResource(f)))
1672 i = IIRCClientFactory(f)
1673 i.nickname = 'fingerbot'
1674 application.connectTCP('irc.freenode.org', 6667, i)
1675 application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f))
1676 </pre>
1677
1678 <p>We add support for perspective broker, Twisted's native remote
1679 object protocol. Now, Twisted clients will not have to go through
1680 XML-RPCish contortions to get information about users.</p>
1681
1682 <h2>Support HTTPS</h2>
1683
1684 <pre>
1685 # Do everything properly, and componentize
1686 from twisted.internet import protocol, reactor, defer, app
1687 from twisted.protocols import basic, irc
1688 from twisted.python import components
1689 from twisted.web import resource, server, static, xmlrpc, microdom
1690 from twisted.web.woven import page, widget
1691 from twisted.spread import pb
1692 from OpenSSL import SSL
1693 import cgi
1694
1695 class IFingerService(components.Interface):
1696
1697     def getUser(self, user):
1698         '''Return a deferred returning a string'''
1699
1700     def getUsers(self):
1701         '''Return a deferred returning a list of strings'''
1702
1703 class IFingerSettingService(components.Interface):
1704
1705     def setUser(self, user, status):
1706         '''Set the user's status to something'''
1707     
1708 def catchError(err):
1709     return "Internal error in server"
1710
1711
1712 class FingerProtocol(basic.LineReceiver):
1713
1714     def lineReceived(self, user):
1715         d = self.factory.getUser(user)
1716         d.addErrback(catchError)
1717         def writeValue(value):
1718             self.transport.write(value)
1719             self.transport.loseConnection()
1720         d.addCallback(writeValue)
1721
1722
1723 class IFingerFactory(components.Interface):
1724
1725     def getUser(self, user):
1726         """Return a deferred returning a string""""
1727
1728     def buildProtocol(self, addr):
1729         """Return a protocol returning a string""""
1730
1731
1732 class FingerFactoryFromService(protocol.ServerFactory):
1733
1734     __implements__ = IFingerFactory,
1735
1736     protocol = FingerProtocol
1737
1738     def __init__(self, service):
1739         self.service = service
1740
1741     def getUser(self, user):
1742         return self.service.getUser(user)
1743
1744 components.registerAdapter(FingerFactoryFromService, IFingerService)
1745
1746
1747 class FingerSetterProtocol(basic.LineReceiver):
1748
1749       def connectionMade(self):
1750           self.lines = []
1751
1752       def lineReceived(self, line):
1753           self.lines.append(line)
1754
1755       def connectionLost(self):
1756           if len(self.lines) == 2:
1757               self.factory.setUser(*self.lines)
1758
1759
1760 class IFingerSetterFactory(components.Interface):
1761
1762     def setUser(self, user, status):
1763         """Return a deferred returning a string"""
1764
1765     def buildProtocol(self, addr):
1766         """Return a protocol returning a string"""
1767
1768
1769 class FingerSetterFactoryFromService(protocol.ServerFactory):
1770
1771     __implements__ = IFingerSetterFactory,
1772
1773     protocol = FingerSetterProtocol
1774
1775     def __init__(self, service):
1776         self.service = service
1777
1778     def setUser(self, user, status):
1779         self.service.setUser(user, status)
1780
1781
1782 components.registerAdapter(FingerSetterFactoryFromService,
1783                            IFingerSettingService)
1784     
1785 class IRCReplyBot(irc.IRCClient):
1786
1787     def connectionMade(self):
1788         self.nickname = self.factory.nickname
1789         irc.IRCClient.connectionMade(self)
1790
1791     def privmsg(self, user, channel, msg):
1792         if user.lower() == channel.lower():
1793             d = self.factory.getUser(msg)
1794             d.addErrback(catchError)
1795             d.addCallback(lambda m: "Status of %s: %s" % (user, m))
1796             d.addCallback(lambda m: self.msg(user, m))
1797
1798
1799 class IIRCClientFactory(components.Interface):
1800
1801     '''
1802     @ivar nickname
1803     '''
1804
1805     def getUser(self, user):
1806         """Return a deferred returning a string"""
1807
1808     def buildProtocol(self, addr):
1809         """Return a protocol"""
1810
1811
1812 class IRCClientFactoryFromService(protocol.ClientFactory):
1813
1814    __implements__ = IIRCClientFactory,
1815
1816    protocol = IRCReplyBot
1817    nickname = None
1818
1819    def __init__(self, service):
1820        self.service = service
1821
1822     def getUser(self, user):
1823         return self.service.getUser()
1824
1825 components.registerAdapter(IRCClientFactoryFromService, IFingerService)
1826
1827
1828 class UsersModel(model.MethodModel):
1829
1830     def __init__(self, service):
1831         self.service = service
1832
1833     def wmfactory_users(self):
1834         return self.service.getUsers()
1835
1836 components.registerAdapter(UsersModel, IFingerService)
1837
1838 class UserStatusTree(page.Page):
1839
1840     template = """&lt;html>&lt;head>&lt;title>Users&lt;/title>&lt;head>&lt;body>
1841                   &lt;h1>Users&lt;/h1>
1842                   &lt;ul model="users" view="List">
1843                   &lt;li pattern="listItem" />&lt;a view="Link" model="."
1844                   href="dummy">&lt;span model="." view="Text" />&lt;/a>
1845                   &lt;/ul>&lt;/body>&lt;/html>"""
1846
1847     def initialize(self, **kwargs):
1848         self.putChild('RPC2.0', UserStatusXR(self.model.service))
1849
1850     def getDynamicChild(self, path, request):
1851         return UserStatus(user=path, service=self.model.service)
1852
1853 components.registerAdapter(UserStatusTree, IFingerService)
1854
1855 class UserStatus(page.Page):
1856
1857     template='''&lt;html>&lt;head>&lt;title view="Text" model="user"/>&lt;/heaD>
1858     &lt;body>&lt;h1 view="Text" model="user"/>
1859     &lt;p mode="status" view="Text" />
1860     &lt;/body>&lt;/html>'''
1861
1862     def initialize(self, **kwargs):
1863         self.user = kwargs['user']
1864         self.service = kwargs['service']
1865
1866     def wmfactory_user(self):
1867         return self.user
1868
1869     def wmfactory_status(self):
1870         return self.service.getUser(self.user)
1871
1872 class UserStatusXR(xmlrpc.XMLPRC):
1873
1874     def __init__(self, service):
1875         xmlrpc.XMLRPC.__init__(self)
1876         self.service = service
1877
1878     def xmlrpc_getUser(self, user):
1879         return self.service.getUser(user)
1880
1881
1882 class IPerspectiveFinger(components.Interface):
1883
1884     def remote_getUser(self, username):
1885         """return a user's status"""
1886
1887     def remote_getUsers(self):
1888         """return a user's status"""
1889
1890
1891 class PerspectiveFingerFromService(pb.Root):
1892
1893     __implements__ = IPerspectiveFinger,
1894
1895     def __init__(self, service):
1896         self.service = service
1897
1898     def remote_getUser(self, username):
1899         return self.service.getUser(username)
1900
1901     def remote_getUsers(self):
1902         return self.service.getUsers()
1903
1904 components.registerAdapter(PerspectiveFingerFromService, IFingerService)
1905
1906
1907 class FingerService(app.ApplicationService):
1908
1909     __implements__ = IFingerService,
1910
1911     def __init__(self, file, *args, **kwargs):
1912         app.ApplicationService.__init__(self, *args, **kwargs)
1913         self.file = file
1914
1915     def startService(self):
1916         app.ApplicationService.startService(self)
1917         self._read()
1918
1919     def _read(self):
1920         self.users = {}
1921         for line in file(self.file):
1922             user, status = line.split(':', 1)
1923             self.users[user] = status
1924         self.call = reactor.callLater(30, self._read)
1925
1926     def stopService(self):
1927         app.ApplicationService.stopService(self)
1928         self.call.cancel()
1929
1930     def getUser(self, user):
1931         return defer.succeed(self.users.get(u, "No such user"))
1932
1933     def getUsers(self):
1934         return defer.succeed(self.users.keys())
1935
1936
1937 class ServerContextFactory:
1938
1939     def getContext(self):
1940         """Create an SSL context.
1941
1942         This is a sample implementation that loads a certificate from a file
1943         called 'server.pem'."""
1944         ctx = SSL.Context(SSL.SSLv23_METHOD)
1945         ctx.use_certificate_file('server.pem')
1946         ctx.use_privatekey_file('server.pem')
1947         return ctx
1948
1949
1950 application = app.Application('finger', uid=1, gid=1)
1951 f = FingerService('/etc/users', application, 'finger')
1952 application.listenTCP(79, IFingerFactory(f))
1953 site = server.Site(resource.IResource(f))
1954 application.listenTCP(80, site)
1955 application.listenSSL(443, site, ServerContextFactory())
1956 i = IIRCClientFactory(f)
1957 i.nickname = 'fingerbot'
1958 application.connectTCP('irc.freenode.org', 6667, i)
1959 application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f))
1960 </pre>
1961
1962 <p>All we need to do to code an HTTPS site is just write a context
1963 factory (in this case, which loads the certificate from a certain file)
1964 and then use the listenSSL method. Note that one factory (in this
1965 case, a site) can listen on multiple ports with multiple protocols.</p>
1966
1967 <h2>Finger Proxy</h2>
1968
1969 <pre>
1970 class FingerClient(protocol.Protocol):
1971
1972     def connectionMade(self):
1973         self.transport.write(self.factory.user+"\r\n")
1974         self.buf = []
1975
1976     def dataReceived(self, data):
1977         self.buf.append(data)
1978
1979     def connectionLost(self):
1980         self.factory.gotData(''.join(self.buf))
1981
1982
1983 class FingerClientFactory(protocol.ClientFactory):
1984
1985     protocol = FingerClient
1986
1987     def __init__(self, user):
1988         self.user = user
1989         self.d = defer.Deferred()
1990
1991     def clientConnectionFailed(self, _, reason):
1992         self.d.errback(reason)
1993
1994     def gotData(self, data):
1995         self.d.callback(data)
1996
1997
1998 def finger(user, host, port=79):
1999     f = FingerClientFactory(user)
2000     reactor.connectTCP(host, port, f)
2001     return f.d
2002   
2003 class ProxyFingerService(app.ApplicationService):
2004     __implements__ = IFingerService
2005
2006     def getUser(self, user):
2007         user, host = user.split('@', 1)
2008         ret = finger(user, host)
2009         ret.addErrback(lambda _: "Could not connect to remote host")
2010         return ret
2011
2012     def getUsers(self):
2013         return defer.succeed([])
2014
2015 application = app.Application('finger', uid=1, gid=1)
2016 f = ProxyFingerService(application, 'finger')
2017 application.listenTCP(79, IFingerFactory(f))
2018 </pre>
2019
2020 <p>Writing new clients with Twisted is much like writing new servers.
2021 We implement the protocol, which just gathers up all the data, and
2022 give it to the factory. The factory keeps a deferred which is triggered
2023 if the connection either fails or succeeds. When we use the client,
2024 we first make sure the deferred will never fail, by producing a message
2025 in that case. Implementing a wrapper around client which just returns
2026 the deferred is a common pattern. While being less flexible than
2027 using the factory directly, it is also more convenient.</p>
2028
2029 <h2>Organization</h2>
2030
2031 <ul>
2032 <li>Code belongs in modules: everything above the <code>application=</code>
2033     line.</li>
2034 <li>Templates belong in separate files. The templateFile attribute can be
2035     used to indicate the file.</li>
2036 <li>The templateDirectory attribute will be used to indicate where to look
2037     for the files.</li>
2038 </ul>
2039
2040 <pre>
2041 from twisted.internet import app
2042 from finger import FingerService, IIRCclient, ServerContextFactory, \
2043                    IFingerFactory, IPerspectiveFinger
2044 from twisted.web import resource, server
2045 from twisted.spread import pb
2046
2047 application = app.Application('finger', uid=1, gid=1)
2048 f = FingerService('/etc/users', application, 'finger')
2049 application.listenTCP(79, IFingerFactory(f))
2050 r = resource.IResource(f)
2051 r.templateDirectory = '/usr/share/finger/templates/'
2052 site = server.Site(r)
2053 application.listenTCP(80, site)
2054 application.listenSSL(443, site, ServerContextFactory())
2055 i = IIRCClientFactory(f)
2056 i.nickname = 'fingerbot'
2057 application.connectTCP('irc.freenode.org', 6667, i)
2058 application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f))
2059 </pre>
2060
2061 <ul>
2062 <li>Seperaration between: code (module), configuration (file above),
2063     presentation (templates), contents (/etc/users), deployment (twistd)</li>
2064 <li>Examples, early prototypes don't need that.</li>
2065 <li>But when writing correctly, easy to do!</li>
2066 </ul>
2067
2068 <h2>Easy Configuration</h2>
2069
2070 <p>We can also supply easy configuration for common cases</p>
2071
2072 <pre>
2073 # in finger.py moudle
2074 def updateApplication(app, **kwargs):
2075      f = FingerService(kwargs['users'], application, 'finger')
2076      application.listenTCP(79, IFingerFactory(f))
2077      r = resource.IResource(f)
2078      r.templateDirectory = kwargs['templates']
2079      site = server.Site(r)
2080      app.listenTCP(80, site)
2081      if kwargs.get('ssl'):
2082          app.listenSSL(443, site, ServerContextFactory())
2083      if kwargs.has_key('ircnick'):
2084          i = IIRCClientFactory(f)
2085          i.nickname = kwargs['ircnick']
2086          ircServer = kwargs['ircserver']
2087          application.connectTCP(ircserver, 6667, i)
2088      if kwargs.has_key('pbport'):
2089          application.listenTCP(int(kwargs['pbport']),
2090                                pb.BrokerFactory(IPerspectiveFinger(f))
2091 </pre>
2092
2093 <p>And we can write simpler files now:</p>
2094
2095 <pre>
2096 # simple-finger.tpy
2097 from twisted.internet import app
2098 import finger 
2099
2100 application = app.Application('finger', uid=1, gid=1)
2101 finger.updateApplication(application,
2102    users='/etc/users',
2103    templatesDirectory='/usr/share/finger/templates',
2104    ssl=1,
2105    ircnick='fingerbot',
2106    ircserver='irc.freenode.net',
2107    pbport=8889
2108 )
2109 </pre>
2110
2111 <p>Note: the finger <em>user</em> still has ultimate power: he can use
2112 updateApplication, or he can use the lower-level interface if he has
2113 specific needs (maybe an ircserver on some other port? maybe we
2114 want the non-ssl webserver to listen only locally? etc. etc.)
2115 This is an important design principle: never force a layer of abstraction:
2116 allow usage of layers of abstractions.</p>
2117
2118 <p>The pasta theory of design:</p>
2119
2120 <ul>
2121 <li>Spaghetti: each piece of code interacts with every other piece of
2122     code [can be implemented with GOTO, functions, objects]</li>
2123 <li>Lasagna: code has carefully designed layers. Each layer is, in
2124     theory independent. However low-level layers usually cannot be
2125     used easily, and high-level layers depend on low-level layers.</li>
2126 <li>Raviolli: each part of the code is useful by itself. There is a thin
2127     layer of interfaces between various parts [the sauce]. Each part
2128     can be usefully be used elsewhere.</li>
2129 <li>...but sometimes, the user just wants to order "Raviolli", so one
2130     coarse-grain easily definable layer of abstraction on top of it all
2131     can be useful.</li>
2132 </ul>
2133
2134 <h2>Plugins</h2>
2135
2136 <p>So far, the user had to be somewhat of a programmer to use this.
2137 Maybe we can eliminate even that? Move old code to
2138 "finger/service.py", put empty "__init__.py" and...</p>
2139
2140 <pre>
2141 # finger/tap.py
2142 from twisted.python import usage
2143 from finger import service
2144
2145 class Options(usage.Options):
2146
2147     optParams = [
2148       ['users', 'u', '/etc/users'],
2149       ['templatesDirectory', 't', '/usr/share/finger/templates'],
2150       ['ircnick', 'n', 'fingerbot'],
2151       ['ircserver', None, 'irc.freenode.net'],
2152       ['pbport', 'p', 8889],
2153     ]
2154
2155     optFlags = [['ssl', 's']]
2156
2157 def updateApplication(app, config):
2158     service.updateApplication(app, **config)
2159 </pre>
2160
2161 <p>And register it all:</p>
2162
2163 <pre>
2164 #finger/plugins.tml
2165 register('Finger', 'finger.tap', type='tap', tapname='finger')
2166 </pre>
2167
2168 <p>And now, the following works</p>
2169
2170 <pre>
2171 % mktap finger --users=/usr/local/etc/users --ircnick=moshez-finger
2172 % sudo twistd -f finger.tap
2173 </pre>
2174
2175 <h2>OS Integration</h2>
2176
2177 <p>If we already have the "finger" package installed, we can achieve
2178 easy integration:</p>
2179
2180 <p>on Debian--</p>
2181
2182 <pre>
2183 % tap2deb --unsigned -m "Foo <foo@example.com>" --type=python finger.tpy
2184 % sudo dpkg -i .build/*.deb
2185 </pre>
2186
2187 <p>On Red Hat [or Mandrake]</p>
2188
2189 <pre>
2190 % tap2rpm --type=python finger.tpy #[maybe other options needed]
2191 % sudo rpm -i .build/*.rpm
2192 </pre>
2193
2194 <p>Will properly register configuration files, init.d sripts, etc. etc.</p>
2195
2196 <p>If it doesn't work on your favourite OS: patches accepted!</p>
2197
2198 <h2>Summary</h2>
2199
2200 <ul>
2201 <li>Twisted is asynchronous</li>
2202 <li>Twisted has implementations of every useful protocol</li>
2203 <li>In Twisted, implementing new protocols is easy [we just did three]</li>
2204 <li>In Twisted, achieving tight integration of servers and clients
2205     is easy.</li>
2206 <li>In Twisted, achieving high code usability is easy.</li>
2207 <li>Ease of use of Twisted follows, in a big part, from that of Python.</li>
2208 <li>Bonus: No buffer overflows. Ever. No matter what.</li>
2209 </ul>
2210
2211 <h2>Motto</h2>
2212
2213 <ul>
2214 <li>"Twisted is not about forcing. It's about mocking you when you use
2215     the technology in suboptimal ways."</li>
2216 <li>You're not forced to use anything except the reactor...</li>
2217 <li>...not the protocol implementations...</li>
2218 <li>...not application...</li>
2219 <li>...not services...</li>
2220 <li>...not components...</li>
2221 <li>...not woven...</li>
2222 <li>...not perspective broker...</li>
2223 <li>...etc.</li>
2224 <li>But you should!</li>
2225 <li>Reinventing the wheel is not a good idea, especially if you form
2226     some vaguely squarish lump of glue and poison and try and attach
2227     it to your car.</li>
2228 <li>The Twisted team solved many of the problems you are likely to come
2229     across...</li>
2230 <li>...several times...</li>
2231 <li>...getting it right the nth time.</li>
2232 </ul>
2233
2234
2235 </body></html>