1
<html><head><title>Evolution of Finger</title></head><body>
2
<h1>Evolution of Finger</h1>
4
<h2>Refuse Connections</h2>
7
from twisted.internet import reactor
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
20
from twisted.internet import protocol, reactor
21
class FingerProtocol(protocol.Protocol):
23
class FingerFactory(protocol.ServerFactory):
24
protocol = FingerProtocol
25
reactor.listenTCP(1079, FingerFactory())
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>
35
<h2>Drop Connections</h2>
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())
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>
58
<h2>Read Username, Drop Connections</h2>
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())
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>
81
<h2>Read Username, Output Error, Drop Connections</h2>
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())
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,
101
<h2>Output From Empty Factory</h2>
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())
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>
124
<h2>Output from Non-empty Factory</h2>
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'))
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>
151
<h2>Use Deferreds</h2>
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'))
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>
179
<h2>Run 'finger' Locally</h2>
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())
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>
205
<h2>Read Status from the Web</h2>
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/~'))
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>
236
<h2>Use Application</h2>
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'))
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>
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
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>
288
<h2>Setting Message By Local Users</h2>
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')
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:
328
Giving a talk now, sorry!
332
<h2>Use Services to Make Dependencies Sane</h2>
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)
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
358
def getFingerSetterFactory(self):
359
f = protocol.ServerFactory()
360
f.protocol, f.setUser = FingerSetterProtocol, self.users.__setitem__
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')
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>
379
<h2>Read Status File</h2>
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)
399
def startService(self):
400
app.ApplicationService.startService(self)
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)
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
417
application = app.Application('finger', uid=1, gid=1)
418
f = FingerService('/etc/users', application, 'finger')
419
application.listenTCP(79, f.getFingerFactory())
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>
427
<h2>Announce on Web, Too</h2>
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
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)
449
def startService(self):
450
app.ApplicationService.startService(self)
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)
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
467
def getResource(self):
468
r = resource.Resource()
469
r.getChild = (lambda path, request:
470
static.Data('text/html',
471
'<h1>%s</h1><p>%s</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()))
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>
487
<h2>Announce on IRC, Too</h2>
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
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)
518
def startService(self):
519
app.ApplicationService.startService(self)
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)
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
536
def getResource(self):
537
r = resource.Resource()
538
r.getChild = (lambda path, request:
539
static.Data('text/html',
540
'<h1>%s</h1><p>%s</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
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'))
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>
563
<h2>Add XML-RPC Support</h2>
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
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)
594
def startService(self):
595
app.ApplicationService.startService(self)
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)
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
612
def getResource(self):
613
r = resource.Resource()
614
r.getChild = (lambda path, request:
615
static.Data('text/html',
616
'<h1>%s</h1><p>%s</p>' %
617
tuple(map(cgi.escape,
618
[path,self.users.get(path, "No such user")]))))
620
x.xmlrpc_getUser = self.getUser
621
r.putChild('RPC2.0', x)
623
def getIRCBot(self, nickname):
624
f = protocol.ReconnectingClientFactory()
625
f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser
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'))
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
642
<h2>Write Readable Code</h2>
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
652
return "Internal error in server"
654
class FingerProtocol(basic.LineReceiver):
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)
665
class FingerSetterProtocol(basic.LineReceiver):
667
def connectionMade(self):
670
def lineReceived(self, line):
671
self.lines.append(line)
673
def connectionLost(self):
674
if len(self.lines) == 2:
675
self.factory.setUser(*self.lines)
678
class IRCReplyBot(irc.IRCClient):
680
def connectionMade(self):
681
self.nickname = self.factory.nickname
682
irc.IRCClient.connectionMade(self)
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))
692
class UserStatusTree(resource.Resource):
694
def __init__(self, service):
695
resource.Resource.__init__(self):
696
self.service = service
698
def render(self, request):
699
d = self.service.getUsers()
700
def formatUsers(users):
701
l = ["<li><a href="%s">%s</a></li> % (user, user)
703
return '<ul>'+''.join(l)+'</ul>'
704
d.addCallback(formatUsers)
705
d.addCallback(request.write)
706
d.addCallback(lambda _: request.finish())
707
return server.NOT_DONE_YET
709
def getChild(self, path, request):
710
return UserStatus(path, self.service)
713
class UserStatus(resource.Resource):
715
def __init__(self, user, service):
716
resource.Resource.__init__(self):
718
self.service = service
720
def render(self, request):
721
d = self.service.getUser(self.user)
722
d.addCallback(cgi.escape)
723
d.addCallback(lambda m:
724
'<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
725
d.addCallback(request.write)
726
d.addCallback(lambda _: request.finish())
727
return server.NOT_DONE_YET
730
class UserStatusXR(xmlrpc.XMLPRC):
732
def __init__(self, service):
733
xmlrpc.XMLRPC.__init__(self)
734
self.service = service
736
def xmlrpc_getUser(self, user):
737
return self.service.getUser(user)
740
class FingerService(app.ApplicationService):
742
def __init__(self, file, *args, **kwargs):
743
app.ApplicationService.__init__(self, *args, **kwargs)
746
def startService(self):
747
app.ApplicationService.startService(self)
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)
757
def stopService(self):
758
app.ApplicationService.stopService(self)
761
def getUser(self, user):
762
return defer.succeed(self.users.get(u, "No such user"))
765
return defer.succeed(self.users.keys())
767
def getFingerFactory(self):
768
f = protocol.ServerFactory()
769
f.protocol = FingerProtocol
770
f.getUser = self.getUser
773
def getResource(self):
774
r = UserStatusTree(self)
775
x = UserStatusXR(self)
776
r.putChild('RPC2.0', x)
779
def getIRCBot(self, nickname):
780
f = protocol.ReconnectingClientFactory()
781
f.protocol = IRCReplyBot
782
f.nickname = nickname
783
f.getUser = self.getUser
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'))
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>
802
<h2>Write Maintainable Code</h2>
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
812
class IFingerService(components.Interface):
814
def getUser(self, user):
815
'''Return a deferred returning a string'''
818
'''Return a deferred returning a list of strings'''
820
class IFingerSettingService(components.Interface):
822
def setUser(self, user, status):
823
'''Set the user's status to something'''
826
return "Internal error in server"
829
class FingerProtocol(basic.LineReceiver):
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)
840
class IFingerFactory(components.Interface):
842
def getUser(self, user):
843
"""Return a deferred returning a string""""
845
def buildProtocol(self, addr):
846
"""Return a protocol returning a string""""
849
class FingerFactoryFromService(protocol.ServerFactory):
851
__implements__ = IFingerFactory,
853
protocol = FingerProtocol
855
def __init__(self, service):
856
self.service = service
858
def getUser(self, user):
859
return self.service.getUser(user)
861
components.registerAdapter(FingerFactoryFromService, IFingerService)
864
class FingerSetterProtocol(basic.LineReceiver):
866
def connectionMade(self):
869
def lineReceived(self, line):
870
self.lines.append(line)
872
def connectionLost(self):
873
if len(self.lines) == 2:
874
self.factory.setUser(*self.lines)
877
class IFingerSetterFactory(components.Interface):
879
def setUser(self, user, status):
880
"""Return a deferred returning a string"""
882
def buildProtocol(self, addr):
883
"""Return a protocol returning a string"""
886
class FingerSetterFactoryFromService(protocol.ServerFactory):
888
__implements__ = IFingerSetterFactory,
890
protocol = FingerSetterProtocol
892
def __init__(self, service):
893
self.service = service
895
def setUser(self, user, status):
896
self.service.setUser(user, status)
899
components.registerAdapter(FingerSetterFactoryFromService,
900
IFingerSettingService)
902
class IRCReplyBot(irc.IRCClient):
904
def connectionMade(self):
905
self.nickname = self.factory.nickname
906
irc.IRCClient.connectionMade(self)
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))
916
class IIRCClientFactory(components.Interface):
922
def getUser(self, user):
923
"""Return a deferred returning a string"""
925
def buildProtocol(self, addr):
926
"""Return a protocol"""
929
class IRCClientFactoryFromService(protocol.ClientFactory):
931
__implements__ = IIRCClientFactory,
933
protocol = IRCReplyBot
936
def __init__(self, service):
937
self.service = service
939
def getUser(self, user):
940
return self.service.getUser()
942
components.registerAdapter(IRCClientFactoryFromService, IFingerService)
944
class UserStatusTree(resource.Resource):
946
def __init__(self, service):
947
resource.Resource.__init__(self):
948
self.putChild('RPC2.0', UserStatusXR(self.service))
949
self.service = service
951
def render(self, request):
952
d = self.service.getUsers()
953
def formatUsers(users):
954
l = ["<li><a href="%s">%s</a></li> % (user, user)
956
return '<ul>'+''.join(l)+'</ul>'
957
d.addCallback(formatUsers)
958
d.addCallback(request.write)
959
d.addCallback(lambda _: request.finish())
960
return server.NOT_DONE_YET
962
def getChild(self, path, request):
963
return UserStatus(path, self.service)
965
components.registerAdapter(UserStatusTree, IFingerService)
967
class UserStatus(resource.Resource):
969
def __init__(self, user, service):
970
resource.Resource.__init__(self):
972
self.service = service
974
def render(self, request):
975
d = self.service.getUser(self.user)
976
d.addCallback(cgi.escape)
977
d.addCallback(lambda m:
978
'<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
979
d.addCallback(request.write)
980
d.addCallback(lambda _: request.finish())
981
return server.NOT_DONE_YET
984
class UserStatusXR(xmlrpc.XMLPRC):
986
def __init__(self, service):
987
xmlrpc.XMLRPC.__init__(self)
988
self.service = service
990
def xmlrpc_getUser(self, user):
991
return self.service.getUser(user)
994
class FingerService(app.ApplicationService):
996
__implements__ = IFingerService,
998
def __init__(self, file, *args, **kwargs):
999
app.ApplicationService.__init__(self, *args, **kwargs)
1002
def startService(self):
1003
app.ApplicationService.startService(self)
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)
1013
def stopService(self):
1014
app.ApplicationService.stopService(self)
1017
def getUser(self, user):
1018
return defer.succeed(self.users.get(u, "No such user"))
1021
return defer.succeed(self.users.keys())
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)
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>
1054
<h2>Advantages of Latest Version</h2>
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>
1064
class MemoryFingerService(app.ApplicationService):
1065
__implements__ = IFingerService, IFingerSetterService
1067
def __init__(self, *args, **kwargs):
1068
app.ApplicationService.__init__(self, *args)
1071
def getUser(self, user):
1072
return defer.succeed(self.users.get(u, "No such user"))
1075
return defer.succeed(self.users.keys())
1077
def setUser(self, user, status):
1078
self.users[user] = status
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')
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>
1098
<h2>Another Backend</h2>
1101
class LocalFingerService(app.ApplicationService):
1102
__implements__ = IFingerService
1104
def getUser(self, user):
1105
return utils.getProcessOutput("finger", [user])
1108
return defer.succeed([])
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)
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>
1122
<h2>Yet Another Backend: Doing the Standard Thing</h2>
1127
class LocalFingerService(app.ApplicationService):
1128
__implements__ = IFingerService
1130
def getUser(self, user):
1132
entry = pwd.getpwnam(user)
1134
return "No such user"
1136
f=file(os.path.join(entry[5],'.plan'))
1137
except (IOError, OSError):
1138
return "No such user"
1144
return defer.succeed([])
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)
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>
1161
<h2>Aspect Oriented Programming</h2>
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>
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
1181
class IFingerService(components.Interface):
1183
def getUser(self, user):
1184
'''Return a deferred returning a string'''
1187
'''Return a deferred returning a list of strings'''
1189
class IFingerSettingService(components.Interface):
1191
def setUser(self, user, status):
1192
'''Set the user's status to something'''
1194
def catchError(err):
1195
return "Internal error in server"
1198
class FingerProtocol(basic.LineReceiver):
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)
1209
class IFingerFactory(components.Interface):
1211
def getUser(self, user):
1212
"""Return a deferred returning a string""""
1214
def buildProtocol(self, addr):
1215
"""Return a protocol returning a string""""
1218
class FingerFactoryFromService(protocol.ServerFactory):
1220
__implements__ = IFingerFactory,
1222
protocol = FingerProtocol
1224
def __init__(self, service):
1225
self.service = service
1227
def getUser(self, user):
1228
return self.service.getUser(user)
1230
components.registerAdapter(FingerFactoryFromService, IFingerService)
1233
class FingerSetterProtocol(basic.LineReceiver):
1235
def connectionMade(self):
1238
def lineReceived(self, line):
1239
self.lines.append(line)
1241
def connectionLost(self):
1242
if len(self.lines) == 2:
1243
self.factory.setUser(*self.lines)
1246
class IFingerSetterFactory(components.Interface):
1248
def setUser(self, user, status):
1249
"""Return a deferred returning a string"""
1251
def buildProtocol(self, addr):
1252
"""Return a protocol returning a string"""
1255
class FingerSetterFactoryFromService(protocol.ServerFactory):
1257
__implements__ = IFingerSetterFactory,
1259
protocol = FingerSetterProtocol
1261
def __init__(self, service):
1262
self.service = service
1264
def setUser(self, user, status):
1265
self.service.setUser(user, status)
1268
components.registerAdapter(FingerSetterFactoryFromService,
1269
IFingerSettingService)
1271
class IRCReplyBot(irc.IRCClient):
1273
def connectionMade(self):
1274
self.nickname = self.factory.nickname
1275
irc.IRCClient.connectionMade(self)
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))
1285
class IIRCClientFactory(components.Interface):
1291
def getUser(self, user):
1292
"""Return a deferred returning a string"""
1294
def buildProtocol(self, addr):
1295
"""Return a protocol"""
1298
class IRCClientFactoryFromService(protocol.ClientFactory):
1300
__implements__ = IIRCClientFactory,
1302
protocol = IRCReplyBot
1305
def __init__(self, service):
1306
self.service = service
1308
def getUser(self, user):
1309
return self.service.getUser()
1311
components.registerAdapter(IRCClientFactoryFromService, IFingerService)
1314
class UsersModel(model.MethodModel):
1316
def __init__(self, service):
1317
self.service = service
1319
def wmfactory_users(self):
1320
return self.service.getUsers()
1322
components.registerAdapter(UsersModel, IFingerService)
1324
class UserStatusTree(page.Page):
1326
template = """<html><head><title>Users</title><head><body>
1327
<h1>Users</h1>
1328
<ul model="users" view="List">
1329
<li pattern="listItem" /><a view="Link" model="."
1330
href="dummy"><span model="." view="Text" /></a>
1331
</ul></body></html>"""
1333
def initialize(self, **kwargs):
1334
self.putChild('RPC2.0', UserStatusXR(self.model.service))
1336
def getDynamicChild(self, path, request):
1337
return UserStatus(user=path, service=self.model.service)
1339
components.registerAdapter(UserStatusTree, IFingerService)
1342
class UserStatus(page.Page):
1344
template='''<html><head><title view="Text" model="user"/></heaD>
1345
<body><h1 view="Text" model="user"/>
1346
<p mode="status" view="Text" />
1347
</body></html>'''
1349
def initialize(self, **kwargs):
1350
self.user = kwargs['user']
1351
self.service = kwargs['service']
1353
def wmfactory_user(self):
1356
def wmfactory_status(self):
1357
return self.service.getUser(self.user)
1359
class UserStatusXR(xmlrpc.XMLPRC):
1361
def __init__(self, service):
1362
xmlrpc.XMLRPC.__init__(self)
1363
self.service = service
1365
def xmlrpc_getUser(self, user):
1366
return self.service.getUser(user)
1369
class FingerService(app.ApplicationService):
1371
__implements__ = IFingerService,
1373
def __init__(self, file, *args, **kwargs):
1374
app.ApplicationService.__init__(self, *args, **kwargs)
1377
def startService(self):
1378
app.ApplicationService.startService(self)
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)
1388
def stopService(self):
1389
app.ApplicationService.stopService(self)
1392
def getUser(self, user):
1393
return defer.succeed(self.users.get(u, "No such user"))
1396
return defer.succeed(self.users.keys())
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)
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>
1413
<h2>Use Perspective Broker</h2>
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
1425
class IFingerService(components.Interface):
1427
def getUser(self, user):
1428
'''Return a deferred returning a string'''
1431
'''Return a deferred returning a list of strings'''
1433
class IFingerSettingService(components.Interface):
1435
def setUser(self, user, status):
1436
'''Set the user's status to something'''
1438
def catchError(err):
1439
return "Internal error in server"
1442
class FingerProtocol(basic.LineReceiver):
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)
1453
class IFingerFactory(components.Interface):
1455
def getUser(self, user):
1456
"""Return a deferred returning a string""""
1458
def buildProtocol(self, addr):
1459
"""Return a protocol returning a string""""
1462
class FingerFactoryFromService(protocol.ServerFactory):
1464
__implements__ = IFingerFactory,
1466
protocol = FingerProtocol
1468
def __init__(self, service):
1469
self.service = service
1471
def getUser(self, user):
1472
return self.service.getUser(user)
1474
components.registerAdapter(FingerFactoryFromService, IFingerService)
1477
class FingerSetterProtocol(basic.LineReceiver):
1479
def connectionMade(self):
1482
def lineReceived(self, line):
1483
self.lines.append(line)
1485
def connectionLost(self):
1486
if len(self.lines) == 2:
1487
self.factory.setUser(*self.lines)
1490
class IFingerSetterFactory(components.Interface):
1492
def setUser(self, user, status):
1493
"""Return a deferred returning a string"""
1495
def buildProtocol(self, addr):
1496
"""Return a protocol returning a string"""
1499
class FingerSetterFactoryFromService(protocol.ServerFactory):
1501
__implements__ = IFingerSetterFactory,
1503
protocol = FingerSetterProtocol
1505
def __init__(self, service):
1506
self.service = service
1508
def setUser(self, user, status):
1509
self.service.setUser(user, status)
1512
components.registerAdapter(FingerSetterFactoryFromService,
1513
IFingerSettingService)
1515
class IRCReplyBot(irc.IRCClient):
1517
def connectionMade(self):
1518
self.nickname = self.factory.nickname
1519
irc.IRCClient.connectionMade(self)
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))
1529
class IIRCClientFactory(components.Interface):
1535
def getUser(self, user):
1536
"""Return a deferred returning a string"""
1538
def buildProtocol(self, addr):
1539
"""Return a protocol"""
1542
class IRCClientFactoryFromService(protocol.ClientFactory):
1544
__implements__ = IIRCClientFactory,
1546
protocol = IRCReplyBot
1549
def __init__(self, service):
1550
self.service = service
1552
def getUser(self, user):
1553
return self.service.getUser()
1555
components.registerAdapter(IRCClientFactoryFromService, IFingerService)
1558
class UsersModel(model.MethodModel):
1560
def __init__(self, service):
1561
self.service = service
1563
def wmfactory_users(self):
1564
return self.service.getUsers()
1566
components.registerAdapter(UsersModel, IFingerService)
1568
class UserStatusTree(page.Page):
1570
template = """<html><head><title>Users</title><head><body>
1571
<h1>Users</h1>
1572
<ul model="users" view="List">
1573
<li pattern="listItem" /><a view="Link" model="."
1574
href="dummy"><span model="." view="Text" /></a>
1575
</ul></body></html>"""
1577
def initialize(self, **kwargs):
1578
self.putChild('RPC2.0', UserStatusXR(self.model.service))
1580
def getDynamicChild(self, path, request):
1581
return UserStatus(user=path, service=self.model.service)
1583
components.registerAdapter(UserStatusTree, IFingerService)
1586
class UserStatus(page.Page):
1588
template='''<html><head><<title view="Text" model="user"/></heaD>
1589
<body><h1 view="Text" model="user"/>
1590
<p mode="status" view="Text" />
1591
</body></html>'''
1593
def initialize(self, **kwargs):
1594
self.user = kwargs['user']
1595
self.service = kwargs['service']
1597
def wmfactory_user(self):
1600
def wmfactory_status(self):
1601
return self.service.getUser(self.user)
1603
class UserStatusXR(xmlrpc.XMLPRC):
1605
def __init__(self, service):
1606
xmlrpc.XMLRPC.__init__(self)
1607
self.service = service
1609
def xmlrpc_getUser(self, user):
1610
return self.service.getUser(user)
1613
class IPerspectiveFinger(components.Interface):
1615
def remote_getUser(self, username):
1616
"""return a user's status"""
1618
def remote_getUsers(self):
1619
"""return a user's status"""
1622
class PerspectiveFingerFromService(pb.Root):
1624
__implements__ = IPerspectiveFinger,
1626
def __init__(self, service):
1627
self.service = service
1629
def remote_getUser(self, username):
1630
return self.service.getUser(username)
1632
def remote_getUsers(self):
1633
return self.service.getUsers()
1635
components.registerAdapter(PerspectiveFingerFromService, IFingerService)
1638
class FingerService(app.ApplicationService):
1640
__implements__ = IFingerService,
1642
def __init__(self, file, *args, **kwargs):
1643
app.ApplicationService.__init__(self, *args, **kwargs)
1646
def startService(self):
1647
app.ApplicationService.startService(self)
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)
1657
def stopService(self):
1658
app.ApplicationService.stopService(self)
1661
def getUser(self, user):
1662
return defer.succeed(self.users.get(u, "No such user"))
1665
return defer.succeed(self.users.keys())
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))
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>
1682
<h2>Support HTTPS</h2>
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
1695
class IFingerService(components.Interface):
1697
def getUser(self, user):
1698
'''Return a deferred returning a string'''
1701
'''Return a deferred returning a list of strings'''
1703
class IFingerSettingService(components.Interface):
1705
def setUser(self, user, status):
1706
'''Set the user's status to something'''
1708
def catchError(err):
1709
return "Internal error in server"
1712
class FingerProtocol(basic.LineReceiver):
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)
1723
class IFingerFactory(components.Interface):
1725
def getUser(self, user):
1726
"""Return a deferred returning a string""""
1728
def buildProtocol(self, addr):
1729
"""Return a protocol returning a string""""
1732
class FingerFactoryFromService(protocol.ServerFactory):
1734
__implements__ = IFingerFactory,
1736
protocol = FingerProtocol
1738
def __init__(self, service):
1739
self.service = service
1741
def getUser(self, user):
1742
return self.service.getUser(user)
1744
components.registerAdapter(FingerFactoryFromService, IFingerService)
1747
class FingerSetterProtocol(basic.LineReceiver):
1749
def connectionMade(self):
1752
def lineReceived(self, line):
1753
self.lines.append(line)
1755
def connectionLost(self):
1756
if len(self.lines) == 2:
1757
self.factory.setUser(*self.lines)
1760
class IFingerSetterFactory(components.Interface):
1762
def setUser(self, user, status):
1763
"""Return a deferred returning a string"""
1765
def buildProtocol(self, addr):
1766
"""Return a protocol returning a string"""
1769
class FingerSetterFactoryFromService(protocol.ServerFactory):
1771
__implements__ = IFingerSetterFactory,
1773
protocol = FingerSetterProtocol
1775
def __init__(self, service):
1776
self.service = service
1778
def setUser(self, user, status):
1779
self.service.setUser(user, status)
1782
components.registerAdapter(FingerSetterFactoryFromService,
1783
IFingerSettingService)
1785
class IRCReplyBot(irc.IRCClient):
1787
def connectionMade(self):
1788
self.nickname = self.factory.nickname
1789
irc.IRCClient.connectionMade(self)
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))
1799
class IIRCClientFactory(components.Interface):
1805
def getUser(self, user):
1806
"""Return a deferred returning a string"""
1808
def buildProtocol(self, addr):
1809
"""Return a protocol"""
1812
class IRCClientFactoryFromService(protocol.ClientFactory):
1814
__implements__ = IIRCClientFactory,
1816
protocol = IRCReplyBot
1819
def __init__(self, service):
1820
self.service = service
1822
def getUser(self, user):
1823
return self.service.getUser()
1825
components.registerAdapter(IRCClientFactoryFromService, IFingerService)
1828
class UsersModel(model.MethodModel):
1830
def __init__(self, service):
1831
self.service = service
1833
def wmfactory_users(self):
1834
return self.service.getUsers()
1836
components.registerAdapter(UsersModel, IFingerService)
1838
class UserStatusTree(page.Page):
1840
template = """<html><head><title>Users</title><head><body>
1841
<h1>Users</h1>
1842
<ul model="users" view="List">
1843
<li pattern="listItem" /><a view="Link" model="."
1844
href="dummy"><span model="." view="Text" /></a>
1845
</ul></body></html>"""
1847
def initialize(self, **kwargs):
1848
self.putChild('RPC2.0', UserStatusXR(self.model.service))
1850
def getDynamicChild(self, path, request):
1851
return UserStatus(user=path, service=self.model.service)
1853
components.registerAdapter(UserStatusTree, IFingerService)
1855
class UserStatus(page.Page):
1857
template='''<html><head><title view="Text" model="user"/></heaD>
1858
<body><h1 view="Text" model="user"/>
1859
<p mode="status" view="Text" />
1860
</body></html>'''
1862
def initialize(self, **kwargs):
1863
self.user = kwargs['user']
1864
self.service = kwargs['service']
1866
def wmfactory_user(self):
1869
def wmfactory_status(self):
1870
return self.service.getUser(self.user)
1872
class UserStatusXR(xmlrpc.XMLPRC):
1874
def __init__(self, service):
1875
xmlrpc.XMLRPC.__init__(self)
1876
self.service = service
1878
def xmlrpc_getUser(self, user):
1879
return self.service.getUser(user)
1882
class IPerspectiveFinger(components.Interface):
1884
def remote_getUser(self, username):
1885
"""return a user's status"""
1887
def remote_getUsers(self):
1888
"""return a user's status"""
1891
class PerspectiveFingerFromService(pb.Root):
1893
__implements__ = IPerspectiveFinger,
1895
def __init__(self, service):
1896
self.service = service
1898
def remote_getUser(self, username):
1899
return self.service.getUser(username)
1901
def remote_getUsers(self):
1902
return self.service.getUsers()
1904
components.registerAdapter(PerspectiveFingerFromService, IFingerService)
1907
class FingerService(app.ApplicationService):
1909
__implements__ = IFingerService,
1911
def __init__(self, file, *args, **kwargs):
1912
app.ApplicationService.__init__(self, *args, **kwargs)
1915
def startService(self):
1916
app.ApplicationService.startService(self)
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)
1926
def stopService(self):
1927
app.ApplicationService.stopService(self)
1930
def getUser(self, user):
1931
return defer.succeed(self.users.get(u, "No such user"))
1934
return defer.succeed(self.users.keys())
1937
class ServerContextFactory:
1939
def getContext(self):
1940
"""Create an SSL context.
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')
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))
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>
1967
<h2>Finger Proxy</h2>
1970
class FingerClient(protocol.Protocol):
1972
def connectionMade(self):
1973
self.transport.write(self.factory.user+"\r\n")
1976
def dataReceived(self, data):
1977
self.buf.append(data)
1979
def connectionLost(self):
1980
self.factory.gotData(''.join(self.buf))
1983
class FingerClientFactory(protocol.ClientFactory):
1985
protocol = FingerClient
1987
def __init__(self, user):
1989
self.d = defer.Deferred()
1991
def clientConnectionFailed(self, _, reason):
1992
self.d.errback(reason)
1994
def gotData(self, data):
1995
self.d.callback(data)
1998
def finger(user, host, port=79):
1999
f = FingerClientFactory(user)
2000
reactor.connectTCP(host, port, f)
2003
class ProxyFingerService(app.ApplicationService):
2004
__implements__ = IFingerService
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")
2013
return defer.succeed([])
2015
application = app.Application('finger', uid=1, gid=1)
2016
f = ProxyFingerService(application, 'finger')
2017
application.listenTCP(79, IFingerFactory(f))
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>
2029
<h2>Organization</h2>
2032
<li>Code belongs in modules: everything above the <code>application=</code>
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
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
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))
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>
2068
<h2>Easy Configuration</h2>
2070
<p>We can also supply easy configuration for common cases</p>
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))
2093
<p>And we can write simpler files now:</p>
2097
from twisted.internet import app
2100
application = app.Application('finger', uid=1, gid=1)
2101
finger.updateApplication(application,
2103
templatesDirectory='/usr/share/finger/templates',
2105
ircnick='fingerbot',
2106
ircserver='irc.freenode.net',
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>
2118
<p>The pasta theory of design:</p>
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
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>
2142
from twisted.python import usage
2143
from finger import service
2145
class Options(usage.Options):
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],
2155
optFlags = [['ssl', 's']]
2157
def updateApplication(app, config):
2158
service.updateApplication(app, **config)
2161
<p>And register it all:</p>
2165
register('Finger', 'finger.tap', type='tap', tapname='finger')
2168
<p>And now, the following works</p>
2171
% mktap finger --users=/usr/local/etc/users --ircnick=moshez-finger
2172
% sudo twistd -f finger.tap
2175
<h2>OS Integration</h2>
2177
<p>If we already have the "finger" package installed, we can achieve
2178
easy integration:</p>
2183
% tap2deb --unsigned -m "Foo <foo@example.com>" --type=python finger.tpy
2184
% sudo dpkg -i .build/*.deb
2187
<p>On Red Hat [or Mandrake]</p>
2190
% tap2rpm --type=python finger.tpy #[maybe other options needed]
2191
% sudo rpm -i .build/*.rpm
2194
<p>Will properly register configuration files, init.d sripts, etc. etc.</p>
2196
<p>If it doesn't work on your favourite OS: patches accepted!</p>
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
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>
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>
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
2230
<li>...several times...</li>
2231
<li>...getting it right the nth time.</li>