~cbehrens/nova/lp844160-build-works-with-zones

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/doc/historic/2003/haifux/haifux.html

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<html><head><title>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>